Merge remote-tracking branch 'egui/master' into dynamic-grid
This commit is contained in:
commit
0f385f6beb
179 changed files with 8173 additions and 3350 deletions
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -20,7 +20,7 @@ eframe = { git = "https://github.com/emilk/egui", branch = "master" }
|
|||
-->
|
||||
|
||||
**Describe the bug**
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
<!-- A clear and concise description of what the bug is. An image is good, a gif or movie is better! -->
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
|
6
.github/workflows/rust.yml
vendored
6
.github/workflows/rust.yml
vendored
|
@ -147,3 +147,9 @@ jobs:
|
|||
- run: sudo apt-get update && sudo apt-get install libspeechd-dev
|
||||
- run: rustup target add wasm32-unknown-unknown
|
||||
- run: cargo doc -p egui_web --target wasm32-unknown-unknown --lib --no-deps --all-features
|
||||
|
||||
cargo-deny:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||
|
|
|
@ -42,7 +42,7 @@ Puts an egui app inside a native window on your laptop. Paints the triangles tha
|
|||
### `eframe`
|
||||
A wrapper around `egui_web` + `egui_glium`, so you can compile the same app for either web or native.
|
||||
|
||||
The demo that you can see at <https://emilk.github.io/egui/index.html> is using `eframe` to host the `egui`. The demo code is found in:
|
||||
The demo that you can see at <https://www.egui.rs> is using `eframe` to host the `egui`. The demo code is found in:
|
||||
|
||||
### `egui_demo_lib`
|
||||
Depends on `egui` + `epi`.
|
||||
|
|
53
CHANGELOG.md
53
CHANGELOG.md
|
@ -8,13 +8,51 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
|||
## Unreleased
|
||||
|
||||
### Added ⭐
|
||||
* Much improved font selection ([#1154](https://github.com/emilk/egui/pull/1154)):
|
||||
* You can now select any font size and family using `RichText::size` amd `RichText::family` and the new `FontId`.
|
||||
* Easily change text styles with `Style::text_styles`.
|
||||
* Added `Ui::text_style_height`.
|
||||
* Added `TextStyle::resolve`.
|
||||
* `Context::load_texture` to convert an image into a texture which can be displayed using e.g. `ui.image(texture, size)` ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||
* Added `Ui::add_visible` and `Ui::add_visible_ui`.
|
||||
* Opt-in dependency on `tracing` crate for logging warnings ([#1192](https://github.com/emilk/egui/pull/1192)).
|
||||
* Added `CollapsingHeader::icon` to override the default open/close icon using a custom function. ([1147](https://github.com/emilk/egui/pull/1147)).
|
||||
* Added `Plot::x_axis_formatter` and `Plot::y_axis_formatter` for custom axis labels ([#1130](https://github.com/emilk/egui/pull/1130)).
|
||||
* Added `ui.data()`, `ctx.data()`, `ctx.options()` and `ctx.tessellation_options()` ([#1175](https://github.com/emilk/egui/pull/1175)).
|
||||
* Added `Plot::allow_boxed_zoom()`, `Plot::boxed_zoom_pointer()` for boxed zooming on plots ([#1188](https://github.com/emilk/egui/pull/1188)).
|
||||
* Added linked axis support for plots via `plot::LinkedAxisGroup` ([#1184](https://github.com/emilk/egui/pull/1184)).
|
||||
* Added `Response::on_hover_text_at_pointer` as a convenience akin to `Response::on_hover_text`. ([1179](https://github.com/emilk/egui/pull/1179))
|
||||
|
||||
### Changed 🔧
|
||||
* ⚠️ `Context::input` and `Ui::input` now locks a mutex. This can lead to a dead-lock is used in an `if let` binding!
|
||||
* `if let Some(pos) = ui.input().pointer.latest_pos()` and similar must now be rewritten on two lines.
|
||||
* Search for this problem in your code using the regex `if let .*input`.
|
||||
* Renamed `CtxRef` to `Context` ([#1050](https://github.com/emilk/egui/pull/1050)).
|
||||
* `Context` can now be cloned and stored between frames ([#1050](https://github.com/emilk/egui/pull/1050)).
|
||||
* Renamed `Ui::visible` to `Ui::is_visible`.
|
||||
* Split `Event::Text` into `Event::Text` and `Event::Paste` ([#1058](https://github.com/emilk/egui/pull/1058)).
|
||||
* For integrations:
|
||||
* `FontImage` has been replaced by `TexturesDelta` (found in `Output`), describing what textures were loaded and freed each frame ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||
* The painter must support partial texture updates ([#1149](https://github.com/emilk/egui/pull/1149)).
|
||||
* Added `RawInput::max_texture_side` which should be filled in with e.g. `GL_MAX_TEXTURE_SIZE` ([#1154](https://github.com/emilk/egui/pull/1154)).
|
||||
* Replaced `Style::body_text_style` with more generic `Style::text_styles` ([#1154](https://github.com/emilk/egui/pull/1154)).
|
||||
* `TextStyle` is no longer `Copy` ([#1154](https://github.com/emilk/egui/pull/1154)).
|
||||
* Replaced `TextEdit::text_style` with `TextEdit::font` ([#1154](https://github.com/emilk/egui/pull/1154)).
|
||||
* Replaced `corner_radius: f32` with `rounding: Rounding`, allowing per-corner rounding settings ([#1206](https://github.com/emilk/egui/pull/1206)).
|
||||
* `Plot::highlight` now takes a `bool` argument ([#1159](https://github.com/emilk/egui/pull/1159)).
|
||||
* `ScrollArea::show` now returns a `ScrollAreaOutput`, so you might need to add `.inner` after the call to it ([#1166](https://github.com/emilk/egui/pull/1166)).
|
||||
|
||||
### Fixed 🐛
|
||||
* Context menu now respects the theme ([#1043](https://github.com/emilk/egui/pull/1043))
|
||||
* Context menus now respects the theme ([#1043](https://github.com/emilk/egui/pull/1043))
|
||||
* Plot `Orientation` was not public, although fields using this type were ([#1130](https://github.com/emilk/egui/pull/1130))
|
||||
* Fixed `enable_drag` for Windows ([#1108](https://github.com/emilk/egui/pull/1108)).
|
||||
* Calling `Context::set_pixels_per_point` before the first frame will now work.
|
||||
|
||||
### Contributors 🙏
|
||||
* [AlexxxRu](https://github.com/alexxxru): [#1108](https://github.com/emilk/egui/pull/1108).
|
||||
* [danielkeller](https://github.com/danielkeller): [#1050](https://github.com/emilk/egui/pull/1050).
|
||||
* [juancampa](https://github.com/juancampa): [#1147](https://github.com/emilk/egui/pull/1147).
|
||||
|
||||
|
||||
## 0.16.1 - 2021-12-31 - Add back `CtxRef::begin_frame,end_frame`
|
||||
|
||||
|
@ -334,7 +372,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
|||
### Changed 🔧
|
||||
* Text will now wrap at newlines, spaces, dashes, punctuation or in the middle of a words if necessary, in that order of priority.
|
||||
* Widgets will now always line break at `\n` characters.
|
||||
* Widgets will now more intelligently choose wether or not to wrap text.
|
||||
* Widgets will now more intelligently choose whether or not to wrap text.
|
||||
* `mouse` has been renamed `pointer` everywhere (to make it clear it includes touches too).
|
||||
* Most parts of `Response` are now methods, so `if ui.button("…").clicked {` is now `if ui.button("…").clicked() {`.
|
||||
* `Response::active` is now gone. You can use `response.dragged()` or `response.clicked()` instead.
|
||||
|
@ -517,6 +555,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
|||
* Optimization: coarse culling in the tessellator
|
||||
* CHANGED: switch argument order of `ui.checkbox` and `ui.radio`
|
||||
|
||||
|
||||
## 0.1.4 - 2020-09-08
|
||||
|
||||
This is when I started the CHANGELOG.md, after almost two years of development. Better late than never.
|
||||
|
@ -528,3 +567,13 @@ This is when I started the CHANGELOG.md, after almost two years of development.
|
|||
* Regions: resizing, vertical scrolling, collapsing headers (sections)
|
||||
* Rendering: Anti-aliased rendering of lines, circles, text and convex polygons.
|
||||
* Tooltips on hover
|
||||
|
||||
|
||||
## Earlier:
|
||||
|
||||
* 2020-08-10: renamed the project to "egui"
|
||||
* 2020-05-30: first release on crates.io (0.1.0)
|
||||
* 2020-05-01: serious work starts (pandemic project)
|
||||
* 2019-03-12: gave a talk about what would later become egui: https://www.youtube.com/watch?v=-pmwLHw5Gbs
|
||||
* 2018-12-23: [initial commit](https://github.com/emilk/egui/commit/856bbf4dae4a69693a0324da34e8b0dd3754dfdf)
|
||||
* 2018-11-04: started tinkering on a train
|
||||
|
|
|
@ -16,7 +16,7 @@ There is an `egui` discord at <https://discord.gg/vbuv9Xan65>.
|
|||
|
||||
## Filing an issue
|
||||
|
||||
[Issues](https://github.com/emilk/egui/issues) are for bug reports and feature requests. Issues are not for asking questions (use [Discussions](https://github.com/emilk/egui/discussions) for that).
|
||||
[Issues](https://github.com/emilk/egui/issues) are for bug reports and feature requests. Issues are not for asking questions (use [Discussions](https://github.com/emilk/egui/discussions) or [Discord](https://discord.gg/vbuv9Xan65) for that).
|
||||
|
||||
Always make sure there is not already a similar issue to the one you are creating.
|
||||
|
||||
|
@ -36,6 +36,8 @@ When you feel the PR is ready to go, do a self-review of the code, and then open
|
|||
|
||||
Please keep pull requests small and focused.
|
||||
|
||||
Don't worry about having many small commits in the PR - they will be squashed to one commit once merged.
|
||||
|
||||
Do not include the `.js` and `.wasm` build artifacts generated for building for web.
|
||||
`git` is not great at storing large files like these, so we only commit a new web demo after a new egui release.
|
||||
|
||||
|
@ -59,6 +61,8 @@ While using an immediate mode gui is simple, implementing one is a lot more tric
|
|||
|
||||
* read some code before writing your own
|
||||
* follow the `egui` code style
|
||||
* add blank lines around all `fn`, `struct`, `enum`, etc.
|
||||
* `// Comment like this`, not `//like this`
|
||||
* write idiomatic rust
|
||||
* avoid `unsafe`
|
||||
* avoid code that can cause panics
|
||||
|
|
1340
Cargo.lock
generated
1340
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -25,3 +25,6 @@ opt-level = 2 # fast and small wasm, basically same as `opt-level = 's'`
|
|||
# opt-level = 3 # unecessarily large wasm for no performance gain
|
||||
|
||||
# debug = true # include debug symbols, useful when profiling wasm
|
||||
|
||||
[patch.crates-io]
|
||||
ureq = { git = "https://github.com/emilk/ureq/", branch = "opt-in-webpki-roots" } # See https://github.com/algesten/ureq/pull/479 / https://github.com/algesten/ureq/issues/478
|
||||
|
|
130
README.md
130
README.md
|
@ -9,6 +9,7 @@
|
|||

|
||||
[](https://discord.gg/JFcEma9bJq)
|
||||
|
||||
👉 [Click to run the web demo](https://www.egui.rs/#demo) 👈
|
||||
|
||||
egui is a simple, fast, and highly portable immediate mode GUI library for Rust. egui runs on the web, natively, and [in your favorite game engine](#integrations) (or will soon).
|
||||
|
||||
|
@ -18,43 +19,19 @@ egui can be used anywhere you can draw textured triangles, which means you can e
|
|||
|
||||
Sections:
|
||||
|
||||
* [Example](#example)
|
||||
* [Quick start](#quick-start)
|
||||
* [Demo](#demo)
|
||||
* [Goals](#goals)
|
||||
* [Who is egui for?](#who-is-egui-for)
|
||||
* [State / features](#state)
|
||||
* [How it works](#how-it-works)
|
||||
* [Integrations](#integrations)
|
||||
* [Why immediate mode](#why-immediate-mode)
|
||||
* [FAQ](#faq)
|
||||
* [Other](#other)
|
||||
* [Credits](#credits)
|
||||
|
||||
## Quick start
|
||||
|
||||
If you just want to write a GUI application in Rust (for the web or for native), go to <https://github.com/emilk/eframe_template/> and follow the instructions there! The official docs are at <https://docs.rs/egui>. For inspiration, check out the [the egui web demo](https://emilk.github.io/egui/index.html) and follow the links in it to its source code. There is also an excellent tutorial video at <https://www.youtube.com/watch?v=NtUkr_z7l84>.
|
||||
|
||||
If you want to integrate egui into an existing engine, go to the [Integrations](#integrations) section.
|
||||
|
||||
If you have questions, use [GitHub Discussions](https://github.com/emilk/egui/discussions). There is also [an egui discord server](https://discord.gg/JFcEma9bJq). If you want to contribute to egui, please read the [Contributing Guidelines](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md).
|
||||
|
||||
## Demo
|
||||
|
||||
[Click to run egui web demo](https://emilk.github.io/egui/index.html) (works in any browser with WASM and WebGL support). Uses [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web).
|
||||
|
||||
To test the demo app locally, run `cargo run --release -p egui_demo_app`.
|
||||
|
||||
The native backend is [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) (using [`glow`](https://crates.io/crates/glow)) and should work out-of-the-box on Mac and Windows, but on Linux you need to first run:
|
||||
|
||||
`sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev`
|
||||
|
||||
On Fedora Rawhide you need to run:
|
||||
|
||||
`dnf install clang clang-devel clang-tools-extra speech-dispatcher-devel libxkbcommon-devel pkg-config openssl-devel libxcb-devel`
|
||||
|
||||
**NOTE**: This is just for the demo app - egui itself is completely platform agnostic!
|
||||
|
||||
### Example
|
||||
## Example
|
||||
|
||||
``` rust
|
||||
ui.heading("My egui Application");
|
||||
|
@ -71,6 +48,30 @@ ui.label(format!("Hello '{}', age {}", name, age));
|
|||
|
||||
<img src="media/demo.gif">
|
||||
|
||||
## Quick start
|
||||
|
||||
If you just want to write a GUI application in Rust (for the web or for native), go to <https://github.com/emilk/eframe_template/> and follow the instructions there! The official docs are at <https://docs.rs/egui>. For inspiration, check out the [the egui web demo](https://www.egui.rs/#demo) and follow the links in it to its source code. There is also an excellent tutorial video at <https://www.youtube.com/watch?v=NtUkr_z7l84>.
|
||||
|
||||
If you want to integrate egui into an existing engine, go to the [Integrations](#integrations) section.
|
||||
|
||||
If you have questions, use [GitHub Discussions](https://github.com/emilk/egui/discussions). There is also [an egui discord server](https://discord.gg/JFcEma9bJq). If you want to contribute to egui, please read the [Contributing Guidelines](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md).
|
||||
|
||||
## Demo
|
||||
|
||||
[Click to run egui web demo](https://www.egui.rs/#demo) (works in any browser with WASM and WebGL support). Uses [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web).
|
||||
|
||||
To test the demo app locally, run `cargo run --release -p egui_demo_app`.
|
||||
|
||||
The native backend is [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) (using [`glow`](https://crates.io/crates/glow)) and should work out-of-the-box on Mac and Windows, but on Linux you need to first run:
|
||||
|
||||
`sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev`
|
||||
|
||||
On Fedora Rawhide you need to run:
|
||||
|
||||
`dnf install clang clang-devel clang-tools-extra speech-dispatcher-devel libxkbcommon-devel pkg-config openssl-devel libxcb-devel`
|
||||
|
||||
**NOTE**: This is just for the demo app - egui itself is completely platform agnostic!
|
||||
|
||||
## Goals
|
||||
|
||||
* The easiest to use GUI library
|
||||
|
@ -145,72 +146,75 @@ Light Theme:
|
|||
|
||||
<img src="media/light_theme.png" width="50%">
|
||||
|
||||
## How it works
|
||||
|
||||
Loop:
|
||||
|
||||
* Gather input (mouse, touches, keyboard, screen size, etc) and give it to egui
|
||||
* Run application code (Immediate Mode GUI)
|
||||
* Tell egui to tessellate the frame graphics to a triangle mesh
|
||||
* Render the triangle mesh with your favorite graphics API (see [OpenGL example](https://github.com/emilk/egui/blob/master/egui_glium/src/painter.rs)) or use `eframe`, the egui framework crate.
|
||||
|
||||
## Integrations
|
||||
|
||||
egui is build to be easy to integrate into any existing game engine or platform you are working on.
|
||||
egui itself doesn't know or care on what OS it is running or how to render things to the screen - that is the job of the egui integration.
|
||||
The integration needs to do two things:
|
||||
|
||||
* **IO**: Supply egui with input (mouse position, keyboard presses, …) and handle egui output (cursor changes, copy-paste integration, …).
|
||||
* **Painting**: Render the textured triangles that egui outputs.
|
||||
An integration needs to do the following each frame:
|
||||
|
||||
### Official
|
||||
* **Input**: Gather input (mouse, touches, keyboard, screen size, etc) and give it to egui
|
||||
* Run the application code
|
||||
* **Output**: Handle egui output (cursor changes, paste, texture allocations, …)
|
||||
* **Painting**: Render the triangle mesh egui produces (see [OpenGL example](https://github.com/emilk/egui/blob/master/egui_glium/src/painter.rs))
|
||||
|
||||
There are three official egui integrations made for apps:
|
||||
### Official integrations
|
||||
|
||||
If you making an app, your best bet is using [`eframe`](https://github.com/emilk/egui/tree/master/eframe), the official egui framework. It lets you write apps that works on both the web and native. `eframe` is just a thin wrapper over `egui_web` and `egui_glium` (see below).
|
||||
|
||||
These are the official egui integrations:
|
||||
|
||||
* [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web) for making a web app. Compiles to WASM, renders with WebGL. [Click to run the egui demo](https://emilk.github.io/egui/index.html).
|
||||
* [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium).
|
||||
* [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) for compiling native apps with [Glow](https://github.com/grovesNL/glow).
|
||||
* [`egui-winit`](https://github.com/emilk/egui/tree/master/egui-winit) for integrating with [`winit`](https://github.com/rust-windowing/winit). `egui-winit` is used by `egui_glium` and `egui_glow`.
|
||||
* [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web) for making a web app. Compiles to WASM, renders with WebGL. [Click to run the egui demo](https://www.egui.rs/#demo).
|
||||
* [`egui-winit`](https://github.com/emilk/egui/tree/master/egui-winit) for integrating with [winit](https://github.com/rust-windowing/winit). `egui-winit` is used by `egui_glium` and `egui_glow`.
|
||||
|
||||
If you making an app, consider using [`eframe`](https://github.com/emilk/egui/tree/master/eframe), a framework which allows you to write code that works on both the web (`egui_web`) and native (using `egui_glium`).
|
||||
|
||||
### 3rd party
|
||||
### 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-miniquad`](https://github.com/not-fl3/egui-miniquad) for [Miniquad](https://github.com/not-fl3/miniquad).
|
||||
* [`egui-macroquad`](https://github.com/optozorax/egui-macroquad) for [macroquad](https://github.com/not-fl3/macroquad).
|
||||
* [`egui_sdl2_gl`](https://crates.io/crates/egui_sdl2_gl) for [SDL2](https://crates.io/crates/sdl2).
|
||||
* [`egui_vulkano`](https://github.com/derivator/egui_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano).
|
||||
* [`egui-winit-ash-integration`](https://github.com/MatchaChoco010/egui-winit-ash-integration) for [winit](https://github.com/rust-windowing/winit) and [ash](https://github.com/MaikKlein/ash).
|
||||
* [`egui_wgpu_backend`](https://crates.io/crates/egui_wgpu_backend) for [wgpu](https://crates.io/crates/wgpu) (WebGPU API).
|
||||
* [`egui_winit_vulkano`](https://github.com/hakolao/egui_winit_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano).
|
||||
* [`egui-macroquad`](https://github.com/optozorax/egui-macroquad) for [macroquad](https://github.com/not-fl3/macroquad).
|
||||
* [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad) for [Miniquad](https://github.com/not-fl3/miniquad).
|
||||
* [`egui-tetra`](https://crates.io/crates/egui-tetra) for [Tetra](https://crates.io/crates/tetra), a 2D game framework.
|
||||
* [`egui-winit-ash-integration`](https://github.com/MatchaChoco010/egui-winit-ash-integration) for [winit](https://github.com/rust-windowing/winit) and [ash](https://github.com/MaikKlein/ash).
|
||||
* [`fltk-egui`](https://crates.io/crates/fltk-egui) for [fltk-rs](https://github.com/fltk-rs/fltk-rs).
|
||||
* [`ggez-egui`](https://github.com/NemuiSen/ggez-egui) for the [ggez](https://ggez.rs/) game framework.
|
||||
* [`godot-egui`](https://github.com/setzer22/godot-egui) for [`godot-rust`](https://github.com/godot-rust/godot-rust).
|
||||
* [`godot-egui`](https://github.com/setzer22/godot-egui) for [godot-rust](https://github.com/godot-rust/godot-rust).
|
||||
* [`nannou_egui`](https://github.com/AlexEne/nannou_egui) for [nannou](https://nannou.cc).
|
||||
* [`egui-tetra`](https://crates.io/crates/egui-tetra) for [Tetra](https://crates.io/crates/tetra), a 2D game framework.
|
||||
* [`egui_wgpu_backend`](https://crates.io/crates/egui_wgpu_backend) for [`wgpu`](https://crates.io/crates/wgpu) (WebGPU API).
|
||||
* [`smithay-egui`](https://github.com/Smithay/smithay-egui) for [smithay](https://github.com/Smithay/smithay/).
|
||||
|
||||
Missing an integration for the thing you're working on? Create one, it is easy!
|
||||
Missing an integration for the thing you're working on? Create one, it's easy!
|
||||
|
||||
### Writing your own egui integration
|
||||
|
||||
You need to collect [`egui::RawInput`](https://docs.rs/egui/latest/egui/struct.RawInput.html), paint [`egui::ClippedMesh`](https://docs.rs/epaint/):es and handle [`egui::Output`](https://docs.rs/egui/latest/egui/struct.Output.html). The basic structure is this:
|
||||
You need to collect [`egui::RawInput`](https://docs.rs/egui/latest/egui/struct.RawInput.html), paint [`egui::ClippedMesh`](https://docs.rs/epaint/latest/epaint/struct.ClippedMesh.html):es and handle [`egui::Output`](https://docs.rs/egui/latest/egui/struct.Output.html). The basic structure is this:
|
||||
|
||||
``` rust
|
||||
let mut egui_ctx = egui::CtxRef::default();
|
||||
|
||||
// Game loop:
|
||||
loop {
|
||||
// Gather input (mouse, touches, keyboard, screen size, etc):
|
||||
let raw_input: egui::RawInput = my_integration.gather_input();
|
||||
let (output, shapes) = egui_ctx.run(raw_input, |egui_ctx| {
|
||||
my_app.ui(egui_ctx); // add panels, windows and widgets to `egui_ctx` here
|
||||
});
|
||||
let clipped_meshes = egui_ctx.tessellate(shapes); // create triangles to paint
|
||||
let clipped_meshes = egui_ctx.tessellate(shapes); // creates triangles to paint
|
||||
|
||||
my_integration.set_egui_textures(&output.textures_delta.set);
|
||||
my_integration.paint(clipped_meshes);
|
||||
my_integration.free_egui_textures(&output.textures_delta.free);
|
||||
|
||||
my_integration.set_cursor_icon(output.cursor_icon);
|
||||
// Also see `egui::Output` for more
|
||||
if !output.copied_text.is_empty() {
|
||||
my_integration.set_clipboard_text(output.copied_text);
|
||||
}
|
||||
// See `egui::Output` for more
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -231,11 +235,12 @@ For a reference OpenGL backend, see [the `egui_glium` painter](https://github.co
|
|||
|
||||
* egui uses premultiplied alpha, so make sure your blending function is `(ONE, ONE_MINUS_SRC_ALPHA)`.
|
||||
* Make sure your texture sampler is clamped (`GL_CLAMP_TO_EDGE`).
|
||||
* Use an sRGBA-aware texture if available (e.g. `GL_SRGB8_ALPHA8`).
|
||||
* Otherwise: remember to decode gamma in the fragment shader.
|
||||
* Decode the gamma of the incoming vertex colors in your vertex shader.
|
||||
* Turn on sRGBA/linear framebuffer if available (`GL_FRAMEBUFFER_SRGB`).
|
||||
* Otherwise: gamma-encode the colors before you write them again.
|
||||
* egui prefers linear color spaces for all blending so:
|
||||
* Use an sRGBA-aware texture if available (e.g. `GL_SRGB8_ALPHA8`).
|
||||
* Otherwise: remember to decode gamma in the fragment shader.
|
||||
* Decode the gamma of the incoming vertex colors in your vertex shader.
|
||||
* Turn on sRGBA/linear framebuffer if available (`GL_FRAMEBUFFER_SRGB`).
|
||||
* Otherwise: gamma-encode the colors before you write them again.
|
||||
|
||||
|
||||
## Why immediate mode
|
||||
|
@ -301,7 +306,7 @@ Yes! But you need to install your own font (`.ttf` or `.otf`) using `Context::se
|
|||
Yes! You can customize the colors, spacing and sizes of everything. By default egui comes with a dark and a light theme.
|
||||
|
||||
### What about accessibility, such as screen readers?
|
||||
There is experimental support for a screen reader. In [the web demo](https://emilk.github.io/egui/index.html) you can enable it in the "Backend" tab.
|
||||
There is experimental support for a screen reader. In [the web demo](https://www.egui.rs/#demo) you can enable it in the "Backend" tab.
|
||||
|
||||
Read more at <https://github.com/emilk/egui/issues/167>.
|
||||
|
||||
|
@ -365,10 +370,13 @@ Notable contributions by:
|
|||
* [@AlexApps99](https://github.com/AlexApps99): [`egui_glow`](https://github.com/emilk/egui/pull/685).
|
||||
* [@mankinskin](https://github.com/mankinskin): [Context menus](https://github.com/emilk/egui/pull/543).
|
||||
* [@t18b219k](https://github.com/t18b219k): [Port glow painter to web](https://github.com/emilk/egui/pull/868).
|
||||
* [@danielkeller](https://github.com/danielkeller): [`Context` refactor](https://github.com/emilk/egui/pull/1050).
|
||||
* And [many more](https://github.com/emilk/egui/graphs/contributors?type=a).
|
||||
|
||||
egui is licensed under [MIT](LICENSE-MIT) OR [Apache-2.0](LICENSE-APACHE).
|
||||
|
||||
* The flattening algorithm for the cubic bezier curve and quadratic bezier curve is from [lyon_geom](https://docs.rs/lyon_geom/latest/lyon_geom/)
|
||||
|
||||
Default fonts:
|
||||
|
||||
* `emoji-icon-font.ttf`: [Copyright (c) 2014 John Slegers](https://github.com/jslegers/emoji-icon-font) , MIT License
|
||||
|
|
68
deny.toml
Normal file
68
deny.toml
Normal file
|
@ -0,0 +1,68 @@
|
|||
# https://embarkstudios.github.io/cargo-deny/
|
||||
|
||||
targets = [
|
||||
{ triple = "aarch64-apple-darwin" },
|
||||
{ triple = "aarch64-linux-android" },
|
||||
{ triple = "x86_64-apple-darwin" },
|
||||
{ triple = "x86_64-pc-windows-msvc" },
|
||||
{ triple = "x86_64-unknown-linux-gnu" },
|
||||
{ triple = "x86_64-unknown-linux-musl" },
|
||||
]
|
||||
|
||||
[advisories]
|
||||
vulnerability = "deny"
|
||||
unmaintained = "warn"
|
||||
yanked = "deny"
|
||||
ignore = [
|
||||
"RUSTSEC-2020-0071", # https://rustsec.org/advisories/RUSTSEC-2020-0071 - chrono/time: Potential segfault in the time crate
|
||||
"RUSTSEC-2020-0159", # https://rustsec.org/advisories/RUSTSEC-2020-0159 - chrono/time: Potential segfault in localtime_r invocations
|
||||
"RUSTSEC-2021-0019", # https://rustsec.org/advisories/RUSTSEC-2021-0019 - xcb - is being worked on: https://github.com/rust-x-bindings/rust-xcb/issues/107
|
||||
]
|
||||
|
||||
[bans]
|
||||
multiple-versions = "deny"
|
||||
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
|
||||
deny = [
|
||||
{ name = "openssl" }, # prefer rustls
|
||||
{ name = "openssl-sys" }, # prefer rustls
|
||||
]
|
||||
|
||||
skip = [
|
||||
{ name = "time" }, # old version pulled in by unmaintianed crate 'chrono'
|
||||
]
|
||||
skip-tree = [
|
||||
{ name = "eframe", version = "0.16.0" },
|
||||
]
|
||||
|
||||
|
||||
[licenses]
|
||||
unlicensed = "deny"
|
||||
allow-osi-fsf-free = "neither"
|
||||
confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text
|
||||
copyleft = "deny"
|
||||
allow = [
|
||||
"Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html
|
||||
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
|
||||
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
|
||||
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
|
||||
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
|
||||
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
|
||||
"ISC", # https://tldrlegal.com/license/-isc-license
|
||||
"MIT", # https://tldrlegal.com/license/mit-license
|
||||
"OpenSSL", # https://www.openssl.org/source/license.html
|
||||
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
|
||||
]
|
||||
|
||||
[[licenses.clarify]]
|
||||
name = "webpki"
|
||||
expression = "ISC"
|
||||
license-files = [
|
||||
{ path = "LICENSE", hash = 0x001c7e6c }
|
||||
]
|
||||
|
||||
[[licenses.clarify]]
|
||||
name = "ring"
|
||||
expression = "MIT AND ISC AND OpenSSL"
|
||||
license-files = [
|
||||
{ path = "LICENSE", hash = 0xbd0eed23 }
|
||||
]
|
1
docs/CNAME
Normal file
1
docs/CNAME
Normal file
|
@ -0,0 +1 @@
|
|||
www.egui.rs
|
|
@ -1,3 +1,3 @@
|
|||
This folder contains the files required for the egui web demo hosted at <https://emilk.github.io/egui/>.
|
||||
This folder contains the files required for the egui web demo hosted at <https://www.egui.rs/>.
|
||||
|
||||
The reason the folder is called "docs" is because that is the name that GitHub requires in order to host a web page from the `master` branch of a repository.
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0; url = https://emilk.github.io/egui/#http" />
|
||||
<meta http-equiv="refresh" content="0; url = https://www.egui.rs/#http" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
transform: translate(-50%, 0%);
|
||||
}
|
||||
|
||||
.loading {
|
||||
.centered {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
display: block;
|
||||
|
@ -54,9 +54,10 @@
|
|||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
color: #f0f0f0;
|
||||
font-size: 24px;
|
||||
font-family: Ubuntu-Light, Helvetica, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------- */
|
||||
|
@ -94,8 +95,10 @@
|
|||
<body>
|
||||
<!-- The WASM code will resize the canvas dynamically -->
|
||||
<canvas id="the_canvas_id"></canvas>
|
||||
<div class="loading" id="loading">
|
||||
Loading…
|
||||
<div class="centered" id="center_text">
|
||||
<p style="font-size:16px">
|
||||
Loading…
|
||||
</p>
|
||||
<div class="lds-dual-ring"></div>
|
||||
</div>
|
||||
|
||||
|
@ -119,18 +122,33 @@
|
|||
// We'll defer our execution until the wasm is ready to go.
|
||||
// Here we tell bindgen the path to the wasm file so it can start
|
||||
// initialization and return to us a promise when it's done.
|
||||
console.debug("loading wasm…");
|
||||
wasm_bindgen("./egui_demo_app_bg.wasm")
|
||||
.then(on_wasm_loaded)
|
||||
.catch(console.error);
|
||||
.catch(on_wasm_error);
|
||||
|
||||
function on_wasm_loaded() {
|
||||
console.log("loaded wasm, starting egui app…");
|
||||
console.debug("wasm loaded. starting egui app…");
|
||||
|
||||
// This call installs a bunch of callbacks and then returns:
|
||||
wasm_bindgen.start("the_canvas_id");
|
||||
|
||||
console.log("egui app started.");
|
||||
document.getElementById("loading").remove();
|
||||
console.debug("egui app started.");
|
||||
document.getElementById("center_text").remove();
|
||||
}
|
||||
|
||||
function on_wasm_error(error) {
|
||||
console.error("Failed to start egui: " + error);
|
||||
document.getElementById("center_text").innerHTML = `
|
||||
<p>
|
||||
An error occurred loading egui
|
||||
</p>
|
||||
<p style="font-family:Courier New">
|
||||
${error}
|
||||
</p>
|
||||
<p style="font-size:14px">
|
||||
Make sure you use a modern browser with WebGL and WASM enabled.
|
||||
</p>`;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
# Changelog for eframe
|
||||
All notable changes to the `eframe` and `epi` crates.
|
||||
|
||||
NOTE: [`egui_web`](egui_web/CHANGELOG.md), [`egui-winit`](egui-winit/CHANGELOG.md), [`egui_glium`](egui_glium/CHANGELOG.md), and [`egui_glow`](egui_glow/CHANGELOG.md) have their own changelogs!
|
||||
NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/CHANGELOG.md), and [`egui_glow`](../egui_glow/CHANGELOG.md) have their own changelogs!
|
||||
|
||||
|
||||
## Unreleased
|
||||
* Removed `Frame::alloc_texture`. Use `egui::Context::load_texture` instead ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||
* The default native backend is now `egui_glow` (instead of `egui_glium`) ([#1020](https://github.com/emilk/egui/pull/1020)).
|
||||
* The default web painter is now `egui_glow` (instead of WebGL) ([#1020](https://github.com/emilk/egui/pull/1020)).
|
||||
* Automatically detect and apply dark or light mode from system ([#1045](https://github.com/emilk/egui/pull/1045)).
|
||||
* Fix horizontal scrolling direction on Linux.
|
||||
* Added `App::on_exit_event` ([#1038](https://github.com/emilk/egui/pull/1038))
|
||||
* Added `NativeOptions::initial_window_pos`.
|
||||
* Shift-scroll will now result in horizontal scrolling on all platforms ([#1136](https://github.com/emilk/egui/pull/1136)).
|
||||
* Log using the `tracing` crate. Log to stdout by adding `tracing_subscriber::fmt::init();` to your `main` ([#1192](https://github.com/emilk/egui/pull/1192)).
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29
|
||||
|
|
|
@ -23,23 +23,6 @@ all-features = true
|
|||
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false }
|
||||
epi = { version = "0.16.0", path = "../epi" }
|
||||
|
||||
# native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
egui-winit = { version = "0.16.0", path = "../egui-winit", default-features = false }
|
||||
egui_glium = { version = "0.16.0", path = "../egui_glium", default-features = false, features = ["clipboard", "epi", "links"], optional = true }
|
||||
egui_glow = { version = "0.16.0", path = "../egui_glow", default-features = false, features = ["clipboard", "epi", "links", "winit"], optional = true }
|
||||
|
||||
# web:
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
egui_web = { version = "0.16.0", path = "../egui_web", default-features = false, features = ["glow"] }
|
||||
|
||||
[dev-dependencies]
|
||||
image = { version = "0.23", default-features = false, features = ["png"] }
|
||||
rfd = "0.6"
|
||||
|
||||
[features]
|
||||
default = ["default_fonts", "egui_glow"]
|
||||
|
@ -57,9 +40,31 @@ persistence = [
|
|||
"epi/persistence",
|
||||
]
|
||||
|
||||
# experimental support for a screen reader
|
||||
# enable screen reader support (requires `ctx.options().screen_reader = true;`)
|
||||
screen_reader = [
|
||||
# we cannot touch egui_glium or egui_glow here due to https://github.com/rust-lang/cargo/issues/8832
|
||||
"egui-winit/screen_reader",
|
||||
"egui_web/screen_reader",
|
||||
]
|
||||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false }
|
||||
epi = { version = "0.16.0", path = "../epi" }
|
||||
|
||||
# native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
egui-winit = { version = "0.16.0", path = "../egui-winit", default-features = false }
|
||||
egui_glium = { version = "0.16.0", path = "../egui_glium", default-features = false, features = ["clipboard", "epi", "links"], optional = true }
|
||||
egui_glow = { version = "0.16.0", path = "../egui_glow", default-features = false, features = ["clipboard", "epi", "links", "winit"], optional = true }
|
||||
|
||||
# web:
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
egui_web = { version = "0.16.0", path = "../egui_web", default-features = false, features = ["glow"] }
|
||||
|
||||
[dev-dependencies]
|
||||
ehttp = "0.2"
|
||||
image = { version = "0.24", default-features = false, features = ["jpeg", "png"] }
|
||||
poll-promise = "0.1"
|
||||
rfd = "0.7"
|
||||
|
||||
|
|
49
eframe/examples/confirm_exit.rs
Normal file
49
eframe/examples/confirm_exit.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::{egui, epi};
|
||||
|
||||
#[derive(Default)]
|
||||
struct MyApp {
|
||||
can_exit: bool,
|
||||
is_exiting: bool,
|
||||
}
|
||||
|
||||
impl epi::App for MyApp {
|
||||
fn name(&self) -> &str {
|
||||
"Confirm exit"
|
||||
}
|
||||
|
||||
fn on_exit_event(&mut self) -> bool {
|
||||
self.is_exiting = true;
|
||||
self.can_exit
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("Try to close the window");
|
||||
});
|
||||
|
||||
if self.is_exiting {
|
||||
egui::Window::new("Do you want to quit?")
|
||||
.collapsible(false)
|
||||
.resizable(false)
|
||||
.show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Yes!").clicked() {
|
||||
self.can_exit = true;
|
||||
frame.quit();
|
||||
}
|
||||
|
||||
if ui.button("Not yet").clicked() {
|
||||
self.is_exiting = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(Box::new(MyApp::default()), options);
|
||||
}
|
|
@ -19,7 +19,7 @@ impl epi::App for MyApp {
|
|||
|
||||
fn setup(
|
||||
&mut self,
|
||||
ctx: &egui::CtxRef,
|
||||
ctx: &egui::Context,
|
||||
_frame: &epi::Frame,
|
||||
_storage: Option<&dyn epi::Storage>,
|
||||
) {
|
||||
|
@ -35,14 +35,14 @@ impl epi::App for MyApp {
|
|||
|
||||
// Put my font first (highest priority) for proportional text:
|
||||
fonts
|
||||
.fonts_for_family
|
||||
.families
|
||||
.entry(egui::FontFamily::Proportional)
|
||||
.or_default()
|
||||
.insert(0, "my_font".to_owned());
|
||||
|
||||
// Put my font as last fallback for monospace:
|
||||
fonts
|
||||
.fonts_for_family
|
||||
.families
|
||||
.entry(egui::FontFamily::Monospace)
|
||||
.or_default()
|
||||
.push("my_font".to_owned());
|
||||
|
@ -51,7 +51,7 @@ impl epi::App for MyApp {
|
|||
ctx.set_fonts(fonts);
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("egui using custom fonts");
|
||||
ui.text_edit_multiline(&mut self.text);
|
||||
|
|
81
eframe/examples/download_image.rs
Normal file
81
eframe/examples/download_image.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::{egui, epi};
|
||||
use poll_promise::Promise;
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(Box::new(MyApp::default()), options);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MyApp {
|
||||
/// `None` when download hasn't started yet.
|
||||
promise: Option<Promise<ehttp::Result<egui::TextureHandle>>>,
|
||||
}
|
||||
|
||||
impl epi::App for MyApp {
|
||||
fn name(&self) -> &str {
|
||||
"Download and show an image with eframe/egui"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
let promise = self.promise.get_or_insert_with(|| {
|
||||
// Begin download.
|
||||
// We download the image using `ehttp`, a library that works both in WASM and on native.
|
||||
// We use the `poll-promise` library to communicate with the UI thread.
|
||||
let ctx = ctx.clone();
|
||||
let frame = frame.clone();
|
||||
let (sender, promise) = Promise::new();
|
||||
let request = ehttp::Request::get("https://picsum.photos/seed/1.759706314/1024");
|
||||
ehttp::fetch(request, move |response| {
|
||||
frame.request_repaint(); // wake up UI thread
|
||||
let texture = response.and_then(|response| parse_response(&ctx, response));
|
||||
sender.send(texture); // send the results back to the UI thread.
|
||||
});
|
||||
promise
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| match promise.ready() {
|
||||
None => {
|
||||
ui.add(egui::Spinner::new()); // still loading
|
||||
}
|
||||
Some(Err(err)) => {
|
||||
ui.colored_label(egui::Color32::RED, err); // something went wrong
|
||||
}
|
||||
Some(Ok(texture)) => {
|
||||
let mut size = texture.size_vec2();
|
||||
size *= (ui.available_width() / size.x).min(1.0);
|
||||
size *= (ui.available_height() / size.y).min(1.0);
|
||||
ui.image(texture, size);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_response(
|
||||
ctx: &egui::Context,
|
||||
response: ehttp::Response,
|
||||
) -> Result<egui::TextureHandle, String> {
|
||||
let content_type = response.content_type().unwrap_or_default();
|
||||
if content_type.starts_with("image/") {
|
||||
let image = load_image(&response.bytes).map_err(|err| err.to_string())?;
|
||||
Ok(ctx.load_texture("my-image", image))
|
||||
} else {
|
||||
Err(format!(
|
||||
"Expected image, found content-type {:?}",
|
||||
content_type
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn load_image(image_data: &[u8]) -> Result<egui::ColorImage, image::ImageError> {
|
||||
let image = image::load_from_memory(image_data)?;
|
||||
let size = [image.width() as _, image.height() as _];
|
||||
let image_buffer = image.to_rgba8();
|
||||
let pixels = image_buffer.as_flat_samples();
|
||||
Ok(egui::ColorImage::from_rgba_unmultiplied(
|
||||
size,
|
||||
pixels.as_slice(),
|
||||
))
|
||||
}
|
|
@ -13,7 +13,7 @@ impl epi::App for MyApp {
|
|||
"Native file dialogs and drag-and-drop files"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.label("Drag-and-drop files onto the window!");
|
||||
|
||||
|
@ -57,7 +57,7 @@ impl epi::App for MyApp {
|
|||
}
|
||||
|
||||
impl MyApp {
|
||||
fn detect_files_being_dropped(&mut self, ctx: &egui::CtxRef) {
|
||||
fn detect_files_being_dropped(&mut self, ctx: &egui::Context) {
|
||||
use egui::*;
|
||||
|
||||
// Preview hovering files:
|
||||
|
@ -82,7 +82,7 @@ impl MyApp {
|
|||
screen_rect.center(),
|
||||
Align2::CENTER_CENTER,
|
||||
text,
|
||||
TextStyle::Heading,
|
||||
TextStyle::Heading.resolve(&ctx.style()),
|
||||
Color32::WHITE,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ impl epi::App for MyApp {
|
|||
"My egui App"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
let Self { name, age } = self;
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
|
|
|
@ -4,7 +4,7 @@ use eframe::{egui, epi};
|
|||
|
||||
#[derive(Default)]
|
||||
struct MyApp {
|
||||
texture: Option<(egui::Vec2, egui::TextureId)>,
|
||||
texture: Option<egui::TextureHandle>,
|
||||
}
|
||||
|
||||
impl epi::App for MyApp {
|
||||
|
@ -12,31 +12,18 @@ impl epi::App for MyApp {
|
|||
"Show an image with eframe/egui"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
if self.texture.is_none() {
|
||||
// Load the image:
|
||||
let image_data = include_bytes!("rust-logo-256x256.png");
|
||||
use image::GenericImageView;
|
||||
let image = image::load_from_memory(image_data).expect("Failed to load image");
|
||||
let image_buffer = image.to_rgba8();
|
||||
let size = [image.width() as usize, image.height() as usize];
|
||||
let pixels = image_buffer.into_vec();
|
||||
let image = epi::Image::from_rgba_unmultiplied(size, &pixels);
|
||||
|
||||
// Allocate a texture:
|
||||
let texture = frame.alloc_texture(image);
|
||||
let size = egui::Vec2::new(size[0] as f32, size[1] as f32);
|
||||
self.texture = Some((size, texture));
|
||||
}
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||
let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
|
||||
let image = load_image(include_bytes!("rust-logo-256x256.png")).unwrap();
|
||||
ctx.load_texture("rust-logo", image)
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if let Some((size, texture)) = self.texture {
|
||||
ui.heading("This is an image:");
|
||||
ui.image(texture, size);
|
||||
ui.heading("This is an image:");
|
||||
ui.image(texture, texture.size_vec2());
|
||||
|
||||
ui.heading("This is an image you can click:");
|
||||
ui.add(egui::ImageButton::new(texture, size));
|
||||
}
|
||||
ui.heading("This is an image you can click:");
|
||||
ui.add(egui::ImageButton::new(texture, texture.size_vec2()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -45,3 +32,14 @@ fn main() {
|
|||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(Box::new(MyApp::default()), options);
|
||||
}
|
||||
|
||||
fn load_image(image_data: &[u8]) -> Result<egui::ColorImage, image::ImageError> {
|
||||
let image = image::load_from_memory(image_data)?;
|
||||
let size = [image.width() as _, image.height() as _];
|
||||
let image_buffer = image.to_rgba8();
|
||||
let pixels = image_buffer.as_flat_samples();
|
||||
Ok(egui::ColorImage::from_rgba_unmultiplied(
|
||||
size,
|
||||
pixels.as_slice(),
|
||||
))
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
//! "My egui App"
|
||||
//! }
|
||||
//!
|
||||
//! fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
//! fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
//! egui::CentralPanel::default().show(ctx, |ui| {
|
||||
//! ui.heading("Hello World!");
|
||||
//! });
|
||||
|
@ -129,7 +129,7 @@ pub fn start_web(canvas_id: &str, app: Box<dyn epi::App>) -> Result<(), wasm_bin
|
|||
/// "My egui App"
|
||||
/// }
|
||||
///
|
||||
/// fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
/// fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
/// egui::CentralPanel::default().show(ctx, |ui| {
|
||||
/// ui.heading("Hello World!");
|
||||
/// });
|
||||
|
@ -160,7 +160,7 @@ pub fn run_native(app: Box<dyn epi::App>, native_options: epi::NativeOptions) ->
|
|||
/// "My egui App"
|
||||
/// }
|
||||
///
|
||||
/// fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
/// fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
/// egui::CentralPanel::default().show(ctx, |ui| {
|
||||
/// ui.heading("Hello World!");
|
||||
/// });
|
||||
|
|
|
@ -4,7 +4,12 @@ All notable changes to the `egui-winit` integration will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* Fixed horizontal scrolling direction on Linux.
|
||||
* Automatically detect and apply dark or light mode from system ([#1045](https://github.com/emilk/egui/pull/1045)).
|
||||
* Replaced `std::time::Instant` with `instant::Instant` for WebAssembly compatability ([#1023](https://github.com/emilk/egui/pull/1023))
|
||||
* Shift-scroll will now result in horizontal scrolling on all platforms ([#1136](https://github.com/emilk/egui/pull/1136)).
|
||||
* Require knowledge about max texture side (e.g. `GL_MAX_TEXTURE_SIZE`)) ([#1154](https://github.com/emilk/egui/pull/1154)).
|
||||
* Fixed `enable_drag` for Windows. Now called only once just after left click ([#1108](https://github.com/emilk/egui/pull/1108)).
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29
|
||||
|
|
|
@ -21,22 +21,9 @@ include = [
|
|||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false, features = ["single_threaded"] }
|
||||
instant = { version = "0.1", features = ["wasm-bindgen"] }
|
||||
winit = "0.26"
|
||||
|
||||
epi = { version = "0.16.0", path = "../epi", optional = true }
|
||||
|
||||
copypasta = { version = "0.7", optional = true }
|
||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||
webbrowser = { version = "0.5", optional = true }
|
||||
|
||||
# feature screen_reader
|
||||
tts = { version = "0.19", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["clipboard", "links"]
|
||||
default = ["clipboard", "dark-light", "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.
|
||||
|
@ -48,8 +35,25 @@ links = ["webbrowser"]
|
|||
# experimental support for a screen reader
|
||||
screen_reader = ["tts"]
|
||||
|
||||
persistence = ["egui/serialize", "serde"]
|
||||
persistence = ["egui/serialize", "serde"] # can't add epi/persistence here because of https://github.com/rust-lang/cargo/issues/8832
|
||||
serialize = ["egui/serialize", "serde"]
|
||||
|
||||
# implement bytemuck on most types.
|
||||
convert_bytemuck = ["egui/convert_bytemuck"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false, features = ["single_threaded", "tracing"] }
|
||||
instant = { version = "0.1", features = ["wasm-bindgen"] }
|
||||
tracing = "0.1"
|
||||
winit = "0.26.1"
|
||||
|
||||
epi = { version = "0.16.0", path = "../epi", optional = true }
|
||||
|
||||
copypasta = { version = "0.7", optional = true }
|
||||
dark-light = { version = "0.2.1", optional = true } # detect dark mode system preference
|
||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||
webbrowser = { version = "0.5", optional = true }
|
||||
|
||||
# feature screen_reader
|
||||
tts = { version = "0.20", optional = true }
|
||||
|
|
|
@ -29,7 +29,7 @@ impl Clipboard {
|
|||
match clipboard.get_contents() {
|
||||
Ok(contents) => Some(contents),
|
||||
Err(err) => {
|
||||
eprintln!("Paste error: {}", err);
|
||||
tracing::error!("Paste error: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ impl Clipboard {
|
|||
if let Some(clipboard) = &mut self.copypasta {
|
||||
use copypasta::ClipboardProvider as _;
|
||||
if let Err(err) = clipboard.set_contents(text) {
|
||||
eprintln!("Copy/Cut error: {}", err);
|
||||
tracing::error!("Copy/Cut error: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ fn init_copypasta() -> Option<copypasta::ClipboardContext> {
|
|||
match copypasta::ClipboardContext::new() {
|
||||
Ok(clipboard) => Some(clipboard),
|
||||
Err(err) => {
|
||||
eprintln!("Failed to initialize clipboard: {}", err);
|
||||
tracing::error!("Failed to initialize clipboard: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,62 @@
|
|||
use egui::Vec2;
|
||||
use winit::dpi::LogicalSize;
|
||||
|
||||
pub fn points_to_size(points: Vec2) -> LogicalSize<f64> {
|
||||
winit::dpi::LogicalSize {
|
||||
width: points.x as f64,
|
||||
height: points.y as f64,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_builder(
|
||||
native_options: &epi::NativeOptions,
|
||||
window_settings: &Option<crate::WindowSettings>,
|
||||
) -> winit::window::WindowBuilder {
|
||||
let window_icon = native_options.icon_data.clone().and_then(load_icon);
|
||||
let epi::NativeOptions {
|
||||
always_on_top,
|
||||
maximized,
|
||||
decorated,
|
||||
drag_and_drop_support,
|
||||
icon_data,
|
||||
initial_window_pos,
|
||||
initial_window_size,
|
||||
min_window_size,
|
||||
max_window_size,
|
||||
resizable,
|
||||
transparent,
|
||||
} = native_options;
|
||||
|
||||
let window_icon = icon_data.clone().and_then(load_icon);
|
||||
|
||||
let mut window_builder = winit::window::WindowBuilder::new()
|
||||
.with_always_on_top(native_options.always_on_top)
|
||||
.with_maximized(native_options.maximized)
|
||||
.with_decorations(native_options.decorated)
|
||||
.with_resizable(native_options.resizable)
|
||||
.with_transparent(native_options.transparent)
|
||||
.with_always_on_top(*always_on_top)
|
||||
.with_maximized(*maximized)
|
||||
.with_decorations(*decorated)
|
||||
.with_resizable(*resizable)
|
||||
.with_transparent(*transparent)
|
||||
.with_window_icon(window_icon);
|
||||
|
||||
window_builder =
|
||||
window_builder_drag_and_drop(window_builder, native_options.drag_and_drop_support);
|
||||
if let Some(min_size) = *min_window_size {
|
||||
window_builder = window_builder.with_min_inner_size(points_to_size(min_size));
|
||||
}
|
||||
if let Some(max_size) = *max_window_size {
|
||||
window_builder = window_builder.with_max_inner_size(points_to_size(max_size));
|
||||
}
|
||||
|
||||
let initial_size_points = native_options.initial_window_size;
|
||||
window_builder = window_builder_drag_and_drop(window_builder, *drag_and_drop_support);
|
||||
|
||||
if let Some(window_settings) = window_settings {
|
||||
window_builder = window_settings.initialize_window(window_builder);
|
||||
} else if let Some(initial_size_points) = initial_size_points {
|
||||
window_builder = window_builder.with_inner_size(winit::dpi::LogicalSize {
|
||||
width: initial_size_points.x as f64,
|
||||
height: initial_size_points.y as f64,
|
||||
});
|
||||
} else {
|
||||
if let Some(pos) = *initial_window_pos {
|
||||
window_builder = window_builder.with_position(winit::dpi::PhysicalPosition {
|
||||
x: pos.x as f64,
|
||||
y: pos.y as f64,
|
||||
});
|
||||
}
|
||||
if let Some(initial_window_size) = *initial_window_size {
|
||||
window_builder = window_builder.with_inner_size(points_to_size(initial_window_size));
|
||||
}
|
||||
}
|
||||
|
||||
window_builder
|
||||
|
@ -55,10 +88,9 @@ pub fn handle_app_output(
|
|||
window: &winit::window::Window,
|
||||
current_pixels_per_point: f32,
|
||||
app_output: epi::backend::AppOutput,
|
||||
) -> epi::backend::TexAllocationData {
|
||||
) {
|
||||
let epi::backend::AppOutput {
|
||||
quit: _,
|
||||
tex_allocation_data,
|
||||
window_size,
|
||||
window_title,
|
||||
decorated,
|
||||
|
@ -86,8 +118,6 @@ pub fn handle_app_output(
|
|||
if drag_window {
|
||||
let _ = window.drag_window();
|
||||
}
|
||||
|
||||
tex_allocation_data
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -191,30 +221,34 @@ impl Persistence {
|
|||
pub struct EpiIntegration {
|
||||
frame: epi::Frame,
|
||||
persistence: crate::epi::Persistence,
|
||||
pub egui_ctx: egui::CtxRef,
|
||||
pub egui_ctx: egui::Context,
|
||||
egui_winit: crate::State,
|
||||
pub app: Box<dyn epi::App>,
|
||||
/// When set, it is time to quit
|
||||
quit: bool,
|
||||
can_drag_window: bool,
|
||||
}
|
||||
|
||||
impl EpiIntegration {
|
||||
pub fn new(
|
||||
integration_name: &'static str,
|
||||
max_texture_side: usize,
|
||||
window: &winit::window::Window,
|
||||
repaint_signal: std::sync::Arc<dyn epi::backend::RepaintSignal>,
|
||||
persistence: crate::epi::Persistence,
|
||||
app: Box<dyn epi::App>,
|
||||
) -> Self {
|
||||
let egui_ctx = egui::CtxRef::default();
|
||||
let egui_ctx = egui::Context::default();
|
||||
|
||||
*egui_ctx.memory() = persistence.load_memory().unwrap_or_default();
|
||||
|
||||
let prefer_dark_mode = prefer_dark_mode();
|
||||
|
||||
let frame = epi::Frame::new(epi::backend::FrameData {
|
||||
info: epi::IntegrationInfo {
|
||||
name: integration_name,
|
||||
web_info: None,
|
||||
prefer_dark_mode: None, // TODO: figure out system default
|
||||
prefer_dark_mode,
|
||||
cpu_usage: None,
|
||||
native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
|
||||
},
|
||||
|
@ -222,13 +256,20 @@ impl EpiIntegration {
|
|||
repaint_signal,
|
||||
});
|
||||
|
||||
if prefer_dark_mode == Some(true) {
|
||||
egui_ctx.set_visuals(egui::Visuals::dark());
|
||||
} else {
|
||||
egui_ctx.set_visuals(egui::Visuals::light());
|
||||
}
|
||||
|
||||
let mut slf = Self {
|
||||
frame,
|
||||
persistence,
|
||||
egui_ctx,
|
||||
egui_winit: crate::State::new(window),
|
||||
egui_winit: crate::State::new(max_texture_side, window),
|
||||
app,
|
||||
quit: false,
|
||||
can_drag_window: false,
|
||||
};
|
||||
|
||||
slf.setup(window);
|
||||
|
@ -243,17 +284,19 @@ impl EpiIntegration {
|
|||
self.app
|
||||
.setup(&self.egui_ctx, &self.frame, self.persistence.storage());
|
||||
let app_output = self.frame.take_app_output();
|
||||
self.quit |= app_output.quit;
|
||||
let tex_alloc_data =
|
||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||
self.frame.lock().output.tex_allocation_data = tex_alloc_data; // Do it later
|
||||
|
||||
if app_output.quit {
|
||||
self.quit = self.app.on_exit_event();
|
||||
}
|
||||
|
||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||
}
|
||||
|
||||
fn warm_up(&mut self, window: &winit::window::Window) {
|
||||
let saved_memory = self.egui_ctx.memory().clone();
|
||||
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
|
||||
self.egui_ctx.memory().set_everything_is_visible(true);
|
||||
let (_, tex_alloc_data, _) = self.update(window);
|
||||
self.frame.lock().output.tex_allocation_data = tex_alloc_data; // handle it next frame
|
||||
let (_, textures_delta, _) = self.update(window);
|
||||
self.egui_ctx.output().textures_delta = textures_delta; // Handle it next frame
|
||||
*self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
|
||||
self.egui_ctx.clear_animations();
|
||||
}
|
||||
|
@ -264,8 +307,19 @@ impl EpiIntegration {
|
|||
}
|
||||
|
||||
pub fn on_event(&mut self, event: &winit::event::WindowEvent<'_>) {
|
||||
use winit::event::WindowEvent;
|
||||
self.quit |= matches!(event, WindowEvent::CloseRequested | WindowEvent::Destroyed);
|
||||
use winit::event::{ElementState, MouseButton, WindowEvent};
|
||||
|
||||
match event {
|
||||
WindowEvent::CloseRequested => self.quit = self.app.on_exit_event(),
|
||||
WindowEvent::Destroyed => self.quit = true,
|
||||
WindowEvent::MouseInput {
|
||||
button: MouseButton::Left,
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
} => self.can_drag_window = true,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.egui_winit.on_event(&self.egui_ctx, event);
|
||||
}
|
||||
|
||||
|
@ -273,11 +327,7 @@ impl EpiIntegration {
|
|||
pub fn update(
|
||||
&mut self,
|
||||
window: &winit::window::Window,
|
||||
) -> (
|
||||
bool,
|
||||
epi::backend::TexAllocationData,
|
||||
Vec<egui::epaint::ClippedShape>,
|
||||
) {
|
||||
) -> (bool, egui::TexturesDelta, Vec<egui::epaint::ClippedShape>) {
|
||||
let frame_start = instant::Instant::now();
|
||||
|
||||
let raw_input = self.egui_winit.take_egui_input(window);
|
||||
|
@ -286,18 +336,24 @@ impl EpiIntegration {
|
|||
});
|
||||
|
||||
let needs_repaint = egui_output.needs_repaint;
|
||||
self.egui_winit
|
||||
let textures_delta = self
|
||||
.egui_winit
|
||||
.handle_output(window, &self.egui_ctx, egui_output);
|
||||
|
||||
let app_output = self.frame.take_app_output();
|
||||
self.quit |= app_output.quit;
|
||||
let tex_allocation_data =
|
||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||
let mut app_output = self.frame.take_app_output();
|
||||
app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108
|
||||
self.can_drag_window = false;
|
||||
|
||||
if app_output.quit {
|
||||
self.quit = self.app.on_exit_event();
|
||||
}
|
||||
|
||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||
|
||||
let frame_time = (instant::Instant::now() - frame_start).as_secs_f64() as f32;
|
||||
self.frame.lock().info.cpu_usage = Some(frame_time);
|
||||
|
||||
(needs_repaint, tex_allocation_data, shapes)
|
||||
(needs_repaint, textures_delta, shapes)
|
||||
}
|
||||
|
||||
pub fn maybe_autosave(&mut self, window: &winit::window::Window) {
|
||||
|
@ -311,3 +367,16 @@ impl EpiIntegration {
|
|||
.save(&mut *self.app, &self.egui_ctx, window);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dark-light")]
|
||||
fn prefer_dark_mode() -> Option<bool> {
|
||||
match dark_light::detect() {
|
||||
dark_light::Mode::Dark => Some(true),
|
||||
dark_light::Mode::Light => Some(false),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "dark-light"))]
|
||||
fn prefer_dark_mode() -> Option<bool> {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -129,17 +129,22 @@ pub struct State {
|
|||
}
|
||||
|
||||
impl State {
|
||||
/// Initialize with the native `pixels_per_point` (dpi scaling).
|
||||
pub fn new(window: &winit::window::Window) -> Self {
|
||||
Self::from_pixels_per_point(native_pixels_per_point(window))
|
||||
/// Initialize with:
|
||||
/// * `max_texture_side`: e.g. `GL_MAX_TEXTURE_SIZE`
|
||||
/// * the native `pixels_per_point` (dpi scaling).
|
||||
pub fn new(max_texture_side: usize, window: &winit::window::Window) -> Self {
|
||||
Self::from_pixels_per_point(max_texture_side, native_pixels_per_point(window))
|
||||
}
|
||||
|
||||
/// Initialize with a given dpi scaling.
|
||||
pub fn from_pixels_per_point(pixels_per_point: f32) -> Self {
|
||||
/// Initialize with:
|
||||
/// * `max_texture_side`: e.g. `GL_MAX_TEXTURE_SIZE`
|
||||
/// * the given `pixels_per_point` (dpi scaling).
|
||||
pub fn from_pixels_per_point(max_texture_side: usize, pixels_per_point: f32) -> Self {
|
||||
Self {
|
||||
start_time: instant::Instant::now(),
|
||||
egui_input: egui::RawInput {
|
||||
pixels_per_point: Some(pixels_per_point),
|
||||
max_texture_side,
|
||||
..Default::default()
|
||||
},
|
||||
pointer_pos_in_points: None,
|
||||
|
@ -453,17 +458,19 @@ impl State {
|
|||
egui::vec2(delta.x as f32, delta.y as f32) / self.pixels_per_point()
|
||||
}
|
||||
};
|
||||
if cfg!(target_os = "macos") {
|
||||
delta.x *= -1.0; // until https://github.com/rust-windowing/winit/pull/2105 is merged and released
|
||||
}
|
||||
if cfg!(target_os = "windows") {
|
||||
delta.x *= -1.0; // until https://github.com/rust-windowing/winit/pull/2101 is merged and released
|
||||
}
|
||||
|
||||
delta.x *= -1.0; // Winit has inverted hscroll. Remove this line when we update winit after https://github.com/rust-windowing/winit/pull/2105 is merged and released
|
||||
|
||||
if self.egui_input.modifiers.ctrl || self.egui_input.modifiers.command {
|
||||
// Treat as zoom instead:
|
||||
let factor = (delta.y / 200.0).exp();
|
||||
self.egui_input.events.push(egui::Event::Zoom(factor));
|
||||
} else if self.egui_input.modifiers.shift {
|
||||
// Treat as horizontal scrolling.
|
||||
// Note: one Mac we already get horizontal scroll events when shift is down.
|
||||
self.egui_input
|
||||
.events
|
||||
.push(egui::Event::Scroll(egui::vec2(delta.x + delta.y, 0.0)));
|
||||
} else {
|
||||
self.egui_input.events.push(egui::Event::Scroll(delta));
|
||||
}
|
||||
|
@ -484,7 +491,7 @@ impl State {
|
|||
if let Some(contents) = self.clipboard.get() {
|
||||
self.egui_input
|
||||
.events
|
||||
.push(egui::Event::Text(contents.replace("\r\n", "\n")));
|
||||
.push(egui::Event::Paste(contents.replace("\r\n", "\n")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -512,26 +519,39 @@ impl State {
|
|||
window: &winit::window::Window,
|
||||
egui_ctx: &egui::Context,
|
||||
output: egui::Output,
|
||||
) {
|
||||
self.current_pixels_per_point = egui_ctx.pixels_per_point(); // someone can have changed it to scale the UI
|
||||
|
||||
if egui_ctx.memory().options.screen_reader {
|
||||
) -> egui::TexturesDelta {
|
||||
if egui_ctx.options().screen_reader {
|
||||
self.screen_reader.speak(&output.events_description());
|
||||
}
|
||||
|
||||
self.set_cursor_icon(window, output.cursor_icon);
|
||||
let egui::Output {
|
||||
cursor_icon,
|
||||
open_url,
|
||||
copied_text,
|
||||
needs_repaint: _, // needs to be handled elsewhere
|
||||
events: _, // handled above
|
||||
mutable_text_under_cursor: _, // only used in egui_web
|
||||
text_cursor_pos,
|
||||
textures_delta,
|
||||
} = output;
|
||||
|
||||
if let Some(open) = output.open_url {
|
||||
open_url(&open.url);
|
||||
self.current_pixels_per_point = egui_ctx.pixels_per_point(); // someone can have changed it to scale the UI
|
||||
|
||||
self.set_cursor_icon(window, cursor_icon);
|
||||
|
||||
if let Some(open_url) = open_url {
|
||||
open_url_in_browser(&open_url.url);
|
||||
}
|
||||
|
||||
if !output.copied_text.is_empty() {
|
||||
self.clipboard.set(output.copied_text);
|
||||
if !copied_text.is_empty() {
|
||||
self.clipboard.set(copied_text);
|
||||
}
|
||||
|
||||
if let Some(egui::Pos2 { x, y }) = output.text_cursor_pos {
|
||||
if let Some(egui::Pos2 { x, y }) = text_cursor_pos {
|
||||
window.set_ime_position(winit::dpi::LogicalPosition { x, y });
|
||||
}
|
||||
|
||||
textures_delta
|
||||
}
|
||||
|
||||
fn set_cursor_icon(&mut self, window: &winit::window::Window, cursor_icon: egui::CursorIcon) {
|
||||
|
@ -554,15 +574,15 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
fn open_url(_url: &str) {
|
||||
fn open_url_in_browser(_url: &str) {
|
||||
#[cfg(feature = "webbrowser")]
|
||||
if let Err(err) = webbrowser::open(_url) {
|
||||
eprintln!("Failed to open url: {}", err);
|
||||
tracing::warn!("Failed to open url: {}", err);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "webbrowser"))]
|
||||
{
|
||||
eprintln!("Cannot open url - feature \"links\" not enabled.");
|
||||
tracing::warn!("Cannot open url - feature \"links\" not enabled.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,11 +15,11 @@ impl Default for ScreenReader {
|
|||
fn default() -> Self {
|
||||
let tts = match tts::Tts::default() {
|
||||
Ok(screen_reader) => {
|
||||
eprintln!("Initialized screen reader.");
|
||||
tracing::debug!("Initialized screen reader.");
|
||||
Some(screen_reader)
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to load screen reader: {}", err);
|
||||
tracing::warn!("Failed to load screen reader: {}", err);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
@ -38,10 +38,10 @@ impl ScreenReader {
|
|||
return;
|
||||
}
|
||||
if let Some(tts) = &mut self.tts {
|
||||
eprintln!("Speaking: {:?}", text);
|
||||
tracing::debug!("Speaking: {:?}", text);
|
||||
let interrupt = true;
|
||||
if let Err(err) = tts.speak(text, interrupt) {
|
||||
eprintln!("Failed to read: {}", err);
|
||||
tracing::warn!("Failed to read: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,13 +23,6 @@ all-features = true
|
|||
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
epaint = { version = "0.16.0", path = "../epaint", default-features = false }
|
||||
|
||||
ahash = "0.7"
|
||||
nohash-hasher = "0.2"
|
||||
ron = { version = "0.7", optional = true }
|
||||
serde = { version = "1", features = ["derive", "rc"], optional = true }
|
||||
|
||||
[features]
|
||||
default = ["default_fonts", "single_threaded"]
|
||||
|
@ -37,6 +30,9 @@ default = ["default_fonts", "single_threaded"]
|
|||
# add compatibility with https://crates.io/crates/cint
|
||||
cint = ["epaint/cint"]
|
||||
|
||||
# implement bytemuck on most types.
|
||||
convert_bytemuck = ["epaint/convert_bytemuck"]
|
||||
|
||||
# If set, egui will use `include_bytes!` to bundle some fonts.
|
||||
# If you plan on specifying your own fonts you may disable this feature.
|
||||
default_fonts = ["epaint/default_fonts"]
|
||||
|
@ -55,10 +51,20 @@ persistence = ["serde", "epaint/serialize", "ron"]
|
|||
# implement serde on most types.
|
||||
serialize = ["serde", "epaint/serialize"]
|
||||
|
||||
# implement bytemuck on most types.
|
||||
convert_bytemuck = ["epaint/convert_bytemuck"]
|
||||
|
||||
# multi_threaded is only needed if you plan to use the same egui::Context
|
||||
# from multiple threads. It comes with a minor performance impact.
|
||||
single_threaded = ["epaint/single_threaded"]
|
||||
multi_threaded = ["epaint/multi_threaded"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
epaint = { version = "0.16.0", path = "../epaint", default-features = false }
|
||||
|
||||
ahash = "0.7"
|
||||
nohash-hasher = "0.2"
|
||||
|
||||
# Optional:
|
||||
ron = { version = "0.7", optional = true }
|
||||
serde = { version = "1", features = ["derive", "rc"], optional = true }
|
||||
# egui doesn't log much, but when it does, it uses `tracing`
|
||||
tracing = { version = "0.1", optional = true }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
There are no stand-alone egui examples, because egui is not stand-alone!
|
||||
|
||||
There are plenty of examples in [the online demo](https://emilk.github.io/egui/). You can find the source code for it at <https://github.com/emilk/egui/tree/master/egui_demo_lib>.
|
||||
There are plenty of examples in [the online demo](https://www.egui.rs/#demo). You can find the source code for it at <https://github.com/emilk/egui/tree/master/egui_demo_lib>.
|
||||
|
||||
If you are using `eframe`, check out [the `eframe` examples](https://github.com/emilk/egui/tree/master/eframe/examples) and [the `eframe` template repository](https://github.com/emilk/eframe_template/).
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::{emath::remap_clamp, Id, IdMap, InputState};
|
|||
#[derive(Clone, Default)]
|
||||
pub(crate) struct AnimationManager {
|
||||
bools: IdMap<BoolAnim>,
|
||||
values: IdMap<ValueAnim>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -12,6 +13,14 @@ struct BoolAnim {
|
|||
toggle_time: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ValueAnim {
|
||||
from_value: f32,
|
||||
to_value: f32,
|
||||
/// when did `value` last toggle?
|
||||
toggle_time: f64,
|
||||
}
|
||||
|
||||
impl AnimationManager {
|
||||
/// See `Context::animate_bool` for documentation
|
||||
pub fn animate_bool(
|
||||
|
@ -56,4 +65,47 @@ impl AnimationManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn animate_value(
|
||||
&mut self,
|
||||
input: &InputState,
|
||||
animation_time: f32,
|
||||
id: Id,
|
||||
value: f32,
|
||||
) -> f32 {
|
||||
match self.values.get_mut(&id) {
|
||||
None => {
|
||||
self.values.insert(
|
||||
id,
|
||||
ValueAnim {
|
||||
from_value: value,
|
||||
to_value: value,
|
||||
toggle_time: -f64::INFINITY, // long time ago
|
||||
},
|
||||
);
|
||||
value
|
||||
}
|
||||
Some(anim) => {
|
||||
let time_since_toggle = (input.time - anim.toggle_time) as f32;
|
||||
// On the frame we toggle we don't want to return the old value,
|
||||
// so we extrapolate forwards:
|
||||
let time_since_toggle = time_since_toggle + input.predicted_dt;
|
||||
let current_value = remap_clamp(
|
||||
time_since_toggle,
|
||||
0.0..=animation_time,
|
||||
anim.from_value..=anim.to_value,
|
||||
);
|
||||
if anim.to_value != value {
|
||||
anim.from_value = current_value; //start new animation from current position of playing animation
|
||||
anim.to_value = value;
|
||||
anim.toggle_time = input.time;
|
||||
}
|
||||
if animation_time == 0.0 {
|
||||
anim.from_value = value;
|
||||
anim.to_value = value;
|
||||
}
|
||||
current_value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -176,7 +176,7 @@ pub(crate) struct Prepared {
|
|||
impl Area {
|
||||
pub fn show<R>(
|
||||
self,
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> InnerResponse<R> {
|
||||
let prepared = self.begin(ctx);
|
||||
|
@ -186,7 +186,7 @@ impl Area {
|
|||
InnerResponse { inner, response }
|
||||
}
|
||||
|
||||
pub(crate) fn begin(self, ctx: &CtxRef) -> Prepared {
|
||||
pub(crate) fn begin(self, ctx: &Context) -> Prepared {
|
||||
let Area {
|
||||
id,
|
||||
movable,
|
||||
|
@ -234,7 +234,7 @@ impl Area {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn show_open_close_animation(&self, ctx: &CtxRef, frame: &Frame, is_open: bool) {
|
||||
pub fn show_open_close_animation(&self, ctx: &Context, frame: &Frame, is_open: bool) {
|
||||
// must be called first so animation managers know the latest state
|
||||
let visibility_factor = ctx.animate_bool(self.id.with("close_animation"), is_open);
|
||||
|
||||
|
@ -276,7 +276,7 @@ impl Prepared {
|
|||
self.drag_bounds
|
||||
}
|
||||
|
||||
pub(crate) fn content_ui(&self, ctx: &CtxRef) -> Ui {
|
||||
pub(crate) fn content_ui(&self, ctx: &Context) -> Ui {
|
||||
let screen_rect = ctx.input().screen_rect();
|
||||
|
||||
let bounds = if let Some(bounds) = self.drag_bounds {
|
||||
|
@ -317,7 +317,7 @@ impl Prepared {
|
|||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)] // intentional to swallow up `content_ui`.
|
||||
pub(crate) fn end(self, ctx: &CtxRef, content_ui: Ui) -> Response {
|
||||
pub(crate) fn end(self, ctx: &Context, content_ui: Ui) -> Response {
|
||||
let Prepared {
|
||||
layer_id,
|
||||
mut state,
|
||||
|
@ -370,8 +370,9 @@ impl Prepared {
|
|||
}
|
||||
|
||||
fn pointer_pressed_on_area(ctx: &Context, layer_id: LayerId) -> bool {
|
||||
if let Some(pointer_pos) = ctx.input().pointer.interact_pos() {
|
||||
ctx.input().pointer.any_pressed() && ctx.layer_id_at(pointer_pos) == Some(layer_id)
|
||||
if let Some(pointer_pos) = ctx.pointer_interact_pos() {
|
||||
let any_pressed = ctx.input().pointer.any_pressed();
|
||||
any_pressed && ctx.layer_id_at(pointer_pos) == Some(layer_id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::hash::Hash;
|
||||
|
||||
use crate::*;
|
||||
use epaint::{Shape, TextStyle};
|
||||
use epaint::Shape;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub(crate) struct State {
|
||||
|
@ -13,22 +13,13 @@ pub(crate) struct State {
|
|||
open_height: Option<f32>,
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
open: false,
|
||||
open_height: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
|
||||
ctx.memory().data.get_persisted(id)
|
||||
ctx.data().get_persisted(id)
|
||||
}
|
||||
|
||||
pub fn store(self, ctx: &Context, id: Id) {
|
||||
ctx.memory().data.insert_persisted(id, self);
|
||||
ctx.data().insert_persisted(id, self);
|
||||
}
|
||||
|
||||
pub fn from_memory_with_default_open(ctx: &Context, id: Id, default_open: bool) -> Self {
|
||||
|
@ -107,7 +98,7 @@ impl State {
|
|||
}
|
||||
|
||||
/// Paint the arrow icon that indicated if the region is open or not
|
||||
pub(crate) fn paint_icon(ui: &mut Ui, openness: f32, response: &Response) {
|
||||
pub(crate) fn paint_default_icon(ui: &mut Ui, openness: f32, response: &Response) {
|
||||
let visuals = ui.style().interact(response);
|
||||
let stroke = visuals.fg_stroke;
|
||||
|
||||
|
@ -126,6 +117,9 @@ pub(crate) fn paint_icon(ui: &mut Ui, openness: f32, response: &Response) {
|
|||
ui.painter().add(Shape::closed_line(points, stroke));
|
||||
}
|
||||
|
||||
/// A function that paints an icon indicating if the region is open or not
|
||||
pub type IconPainter = Box<dyn FnOnce(&mut Ui, f32, &Response)>;
|
||||
|
||||
/// A header which can be collapsed/expanded, revealing a contained [`Ui`] region.
|
||||
///
|
||||
///
|
||||
|
@ -150,6 +144,7 @@ pub struct CollapsingHeader {
|
|||
selectable: bool,
|
||||
selected: bool,
|
||||
show_background: bool,
|
||||
icon: Option<IconPainter>,
|
||||
}
|
||||
|
||||
impl CollapsingHeader {
|
||||
|
@ -171,6 +166,7 @@ impl CollapsingHeader {
|
|||
selectable: false,
|
||||
selected: false,
|
||||
show_background: false,
|
||||
icon: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,6 +241,28 @@ impl CollapsingHeader {
|
|||
self.show_background = show_background;
|
||||
self
|
||||
}
|
||||
|
||||
/// Use the provided function to render a different `CollapsingHeader` icon.
|
||||
/// Defaults to a triangle that animates as the `CollapsingHeader` opens and closes.
|
||||
///
|
||||
/// For example:
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// fn circle_icon(ui: &mut egui::Ui, openness: f32, response: &egui::Response) {
|
||||
/// let stroke = ui.style().interact(&response).fg_stroke;
|
||||
/// let radius = egui::lerp(2.0..=3.0, openness);
|
||||
/// ui.painter().circle_filled(response.rect.center(), radius, stroke.color);
|
||||
/// }
|
||||
///
|
||||
/// egui::CollapsingHeader::new("Circles")
|
||||
/// .icon(circle_icon)
|
||||
/// .show(ui, |ui| { ui.label("Hi!"); });
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn icon(mut self, icon_fn: impl FnOnce(&mut Ui, f32, &Response) + 'static) -> Self {
|
||||
self.icon = Some(Box::new(icon_fn));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct Prepared {
|
||||
|
@ -260,6 +278,7 @@ impl CollapsingHeader {
|
|||
"Horizontal collapsing is unimplemented"
|
||||
);
|
||||
let Self {
|
||||
icon,
|
||||
text,
|
||||
default_open,
|
||||
open,
|
||||
|
@ -319,7 +338,7 @@ impl CollapsingHeader {
|
|||
if ui.visuals().collapsing_header_frame || self.show_background {
|
||||
ui.painter().add(epaint::RectShape {
|
||||
rect: header_response.rect.expand(visuals.expansion),
|
||||
corner_radius: visuals.corner_radius,
|
||||
rounding: visuals.rounding,
|
||||
fill: visuals.bg_fill,
|
||||
stroke: visuals.bg_stroke,
|
||||
// stroke: Default::default(),
|
||||
|
@ -331,9 +350,8 @@ impl CollapsingHeader {
|
|||
{
|
||||
let rect = rect.expand(visuals.expansion);
|
||||
|
||||
let corner_radius = 2.0;
|
||||
ui.painter()
|
||||
.rect(rect, corner_radius, visuals.bg_fill, visuals.bg_stroke);
|
||||
.rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke);
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -347,7 +365,11 @@ impl CollapsingHeader {
|
|||
..header_response.clone()
|
||||
};
|
||||
let openness = state.openness(ui.ctx(), id);
|
||||
paint_icon(ui, openness, &icon_response);
|
||||
if let Some(icon) = icon {
|
||||
icon(ui, openness, &icon_response);
|
||||
} else {
|
||||
paint_default_icon(ui, openness, &icon_response);
|
||||
}
|
||||
}
|
||||
|
||||
text.paint_with_visuals(ui.painter(), text_pos, &visuals);
|
||||
|
|
|
@ -27,6 +27,16 @@ pub struct ComboBox {
|
|||
}
|
||||
|
||||
impl ComboBox {
|
||||
/// Create new `ComboBox` with id and label
|
||||
pub fn new(id_source: impl std::hash::Hash, label: impl Into<WidgetText>) -> Self {
|
||||
Self {
|
||||
id_source: Id::new(id_source),
|
||||
label: Some(label.into()),
|
||||
selected_text: Default::default(),
|
||||
width: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Label shown next to the combo box
|
||||
pub fn from_label(label: impl Into<WidgetText>) -> Self {
|
||||
let label = label.into();
|
||||
|
@ -196,6 +206,7 @@ fn combo_box_dyn<'c, R>(
|
|||
ScrollArea::vertical()
|
||||
.max_height(ui.spacing().combo_height)
|
||||
.show(ui, menu_contents)
|
||||
.inner
|
||||
});
|
||||
|
||||
InnerResponse {
|
||||
|
@ -239,7 +250,7 @@ fn button_frame(
|
|||
where_to_put_background,
|
||||
epaint::RectShape {
|
||||
rect: outer_rect.expand(visuals.expansion),
|
||||
corner_radius: visuals.corner_radius,
|
||||
rounding: visuals.rounding,
|
||||
fill: visuals.bg_fill,
|
||||
stroke: visuals.bg_stroke,
|
||||
},
|
||||
|
|
|
@ -9,7 +9,7 @@ use epaint::*;
|
|||
pub struct Frame {
|
||||
/// On each side
|
||||
pub margin: Vec2,
|
||||
pub corner_radius: f32,
|
||||
pub rounding: Rounding,
|
||||
pub shadow: Shadow,
|
||||
pub fill: Color32,
|
||||
pub stroke: Stroke,
|
||||
|
@ -24,7 +24,7 @@ impl Frame {
|
|||
pub fn group(style: &Style) -> Self {
|
||||
Self {
|
||||
margin: Vec2::splat(6.0), // symmetric looks best in corners when nesting
|
||||
corner_radius: style.visuals.widgets.noninteractive.corner_radius,
|
||||
rounding: style.visuals.widgets.noninteractive.rounding,
|
||||
stroke: style.visuals.widgets.noninteractive.bg_stroke,
|
||||
..Default::default()
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ impl Frame {
|
|||
pub(crate) fn side_top_panel(style: &Style) -> Self {
|
||||
Self {
|
||||
margin: Vec2::new(8.0, 2.0),
|
||||
corner_radius: 0.0,
|
||||
rounding: Rounding::none(),
|
||||
fill: style.visuals.window_fill(),
|
||||
stroke: style.visuals.window_stroke(),
|
||||
..Default::default()
|
||||
|
@ -43,7 +43,7 @@ impl Frame {
|
|||
pub(crate) fn central_panel(style: &Style) -> Self {
|
||||
Self {
|
||||
margin: Vec2::new(8.0, 8.0),
|
||||
corner_radius: 0.0,
|
||||
rounding: Rounding::none(),
|
||||
fill: style.visuals.window_fill(),
|
||||
stroke: Default::default(),
|
||||
..Default::default()
|
||||
|
@ -53,7 +53,7 @@ impl Frame {
|
|||
pub fn window(style: &Style) -> Self {
|
||||
Self {
|
||||
margin: style.spacing.window_padding,
|
||||
corner_radius: style.visuals.window_corner_radius,
|
||||
rounding: style.visuals.window_rounding,
|
||||
shadow: style.visuals.window_shadow,
|
||||
fill: style.visuals.window_fill(),
|
||||
stroke: style.visuals.window_stroke(),
|
||||
|
@ -63,7 +63,7 @@ impl Frame {
|
|||
pub fn menu(style: &Style) -> Self {
|
||||
Self {
|
||||
margin: Vec2::splat(1.0),
|
||||
corner_radius: style.visuals.widgets.noninteractive.corner_radius,
|
||||
rounding: style.visuals.widgets.noninteractive.rounding,
|
||||
shadow: style.visuals.popup_shadow,
|
||||
fill: style.visuals.window_fill(),
|
||||
stroke: style.visuals.window_stroke(),
|
||||
|
@ -73,7 +73,7 @@ impl Frame {
|
|||
pub fn popup(style: &Style) -> Self {
|
||||
Self {
|
||||
margin: style.spacing.window_padding,
|
||||
corner_radius: style.visuals.widgets.noninteractive.corner_radius,
|
||||
rounding: style.visuals.widgets.noninteractive.rounding,
|
||||
shadow: style.visuals.popup_shadow,
|
||||
fill: style.visuals.window_fill(),
|
||||
stroke: style.visuals.window_stroke(),
|
||||
|
@ -84,7 +84,7 @@ impl Frame {
|
|||
pub fn dark_canvas(style: &Style) -> Self {
|
||||
Self {
|
||||
margin: Vec2::new(10.0, 10.0),
|
||||
corner_radius: style.visuals.widgets.noninteractive.corner_radius,
|
||||
rounding: style.visuals.widgets.noninteractive.rounding,
|
||||
fill: Color32::from_black_alpha(250),
|
||||
stroke: style.visuals.window_stroke(),
|
||||
..Default::default()
|
||||
|
@ -103,8 +103,8 @@ impl Frame {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn corner_radius(mut self, corner_radius: f32) -> Self {
|
||||
self.corner_radius = corner_radius;
|
||||
pub fn rounding(mut self, rounding: impl Into<Rounding>) -> Self {
|
||||
self.rounding = rounding.into();
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -172,7 +172,7 @@ impl Frame {
|
|||
pub fn paint(&self, outer_rect: Rect) -> Shape {
|
||||
let Self {
|
||||
margin: _,
|
||||
corner_radius,
|
||||
rounding,
|
||||
shadow,
|
||||
fill,
|
||||
stroke,
|
||||
|
@ -180,7 +180,7 @@ impl Frame {
|
|||
|
||||
let frame_shape = Shape::Rect(epaint::RectShape {
|
||||
rect: outer_rect,
|
||||
corner_radius,
|
||||
rounding,
|
||||
fill,
|
||||
stroke,
|
||||
});
|
||||
|
@ -188,7 +188,7 @@ impl Frame {
|
|||
if shadow == Default::default() {
|
||||
frame_shape
|
||||
} else {
|
||||
let shadow = shadow.tessellate(outer_rect, corner_radius);
|
||||
let shadow = shadow.tessellate(outer_rect, rounding);
|
||||
let shadow = Shape::Mesh(shadow);
|
||||
Shape::Vec(vec![shadow, frame_shape])
|
||||
}
|
||||
|
|
|
@ -25,11 +25,11 @@ struct PanelState {
|
|||
|
||||
impl PanelState {
|
||||
fn load(ctx: &Context, bar_id: Id) -> Option<Self> {
|
||||
ctx.memory().data.get_persisted(bar_id)
|
||||
ctx.data().get_persisted(bar_id)
|
||||
}
|
||||
|
||||
fn store(self, ctx: &Context, bar_id: Id) {
|
||||
ctx.memory().data.insert_persisted(bar_id, self);
|
||||
ctx.data().insert_persisted(bar_id, self);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,7 +199,7 @@ impl SidePanel {
|
|||
let mut is_resizing = false;
|
||||
if resizable {
|
||||
let resize_id = id.with("__resize");
|
||||
if let Some(pointer) = ui.input().pointer.latest_pos() {
|
||||
if let Some(pointer) = ui.ctx().latest_pointer_pos() {
|
||||
let we_are_on_top = ui
|
||||
.ctx()
|
||||
.layer_id_at(pointer)
|
||||
|
@ -284,7 +284,7 @@ impl SidePanel {
|
|||
/// Show the panel at the top level.
|
||||
pub fn show<R>(
|
||||
self,
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> InnerResponse<R> {
|
||||
self.show_dyn(ctx, Box::new(add_contents))
|
||||
|
@ -293,7 +293,7 @@ impl SidePanel {
|
|||
/// Show the panel at the top level.
|
||||
fn show_dyn<'c, R>(
|
||||
self,
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
) -> InnerResponse<R> {
|
||||
let layer_id = LayerId::background();
|
||||
|
@ -485,7 +485,8 @@ impl TopBottomPanel {
|
|||
let mut is_resizing = false;
|
||||
if resizable {
|
||||
let resize_id = id.with("__resize");
|
||||
if let Some(pointer) = ui.input().pointer.latest_pos() {
|
||||
let latest_pos = ui.input().pointer.latest_pos();
|
||||
if let Some(pointer) = latest_pos {
|
||||
let we_are_on_top = ui
|
||||
.ctx()
|
||||
.layer_id_at(pointer)
|
||||
|
@ -570,7 +571,7 @@ impl TopBottomPanel {
|
|||
/// Show the panel at the top level.
|
||||
pub fn show<R>(
|
||||
self,
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> InnerResponse<R> {
|
||||
self.show_dyn(ctx, Box::new(add_contents))
|
||||
|
@ -579,7 +580,7 @@ impl TopBottomPanel {
|
|||
/// Show the panel at the top level.
|
||||
fn show_dyn<'c, R>(
|
||||
self,
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
) -> InnerResponse<R> {
|
||||
let layer_id = LayerId::background();
|
||||
|
@ -670,7 +671,7 @@ impl CentralPanel {
|
|||
/// Show the panel at the top level.
|
||||
pub fn show<R>(
|
||||
self,
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> InnerResponse<R> {
|
||||
self.show_dyn(ctx, Box::new(add_contents))
|
||||
|
@ -679,7 +680,7 @@ impl CentralPanel {
|
|||
/// Show the panel at the top level.
|
||||
fn show_dyn<'c, R>(
|
||||
self,
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
) -> InnerResponse<R> {
|
||||
let available_rect = ctx.available_rect();
|
||||
|
|
|
@ -13,11 +13,11 @@ pub(crate) struct MonoState {
|
|||
|
||||
impl MonoState {
|
||||
fn load(ctx: &Context) -> Option<Self> {
|
||||
ctx.memory().data.get_temp(Id::null())
|
||||
ctx.data().get_temp(Id::null())
|
||||
}
|
||||
|
||||
fn store(self, ctx: &Context) {
|
||||
ctx.memory().data.insert_temp(Id::null(), self);
|
||||
ctx.data().insert_temp(Id::null(), self);
|
||||
}
|
||||
|
||||
fn tooltip_size(&self, id: Id, index: usize) -> Option<Vec2> {
|
||||
|
@ -66,7 +66,11 @@ impl MonoState {
|
|||
/// }
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn show_tooltip<R>(ctx: &CtxRef, id: Id, add_contents: impl FnOnce(&mut Ui) -> R) -> Option<R> {
|
||||
pub fn show_tooltip<R>(
|
||||
ctx: &Context,
|
||||
id: Id,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> Option<R> {
|
||||
show_tooltip_at_pointer(ctx, id, add_contents)
|
||||
}
|
||||
|
||||
|
@ -88,7 +92,7 @@ pub fn show_tooltip<R>(ctx: &CtxRef, id: Id, add_contents: impl FnOnce(&mut Ui)
|
|||
/// # });
|
||||
/// ```
|
||||
pub fn show_tooltip_at_pointer<R>(
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
id: Id,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> Option<R> {
|
||||
|
@ -104,7 +108,7 @@ pub fn show_tooltip_at_pointer<R>(
|
|||
///
|
||||
/// If the tooltip does not fit under the area, it tries to place it above it instead.
|
||||
pub fn show_tooltip_for<R>(
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
id: Id,
|
||||
rect: &Rect,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
|
@ -129,7 +133,7 @@ pub fn show_tooltip_for<R>(
|
|||
///
|
||||
/// Returns `None` if the tooltip could not be placed.
|
||||
pub fn show_tooltip_at<R>(
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
id: Id,
|
||||
suggested_position: Option<Pos2>,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
|
@ -146,7 +150,7 @@ pub fn show_tooltip_at<R>(
|
|||
}
|
||||
|
||||
fn show_tooltip_at_avoid_dyn<'c, R>(
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
mut id: Id,
|
||||
suggested_position: Option<Pos2>,
|
||||
above: bool,
|
||||
|
@ -229,7 +233,7 @@ fn show_tooltip_at_avoid_dyn<'c, R>(
|
|||
/// }
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn show_tooltip_text(ctx: &CtxRef, id: Id, text: impl Into<WidgetText>) -> Option<()> {
|
||||
pub fn show_tooltip_text(ctx: &Context, id: Id, text: impl Into<WidgetText>) -> Option<()> {
|
||||
show_tooltip(ctx, id, |ui| {
|
||||
crate::widgets::Label::new(text).ui(ui);
|
||||
})
|
||||
|
@ -237,7 +241,7 @@ pub fn show_tooltip_text(ctx: &CtxRef, id: Id, text: impl Into<WidgetText>) -> O
|
|||
|
||||
/// Show a pop-over window.
|
||||
fn show_tooltip_area_dyn<'c, R>(
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
id: Id,
|
||||
window_pos: Pos2,
|
||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
|
|
|
@ -18,11 +18,11 @@ pub(crate) struct State {
|
|||
|
||||
impl State {
|
||||
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
|
||||
ctx.memory().data.get_persisted(id)
|
||||
ctx.data().get_persisted(id)
|
||||
}
|
||||
|
||||
pub fn store(self, ctx: &Context, id: Id) {
|
||||
ctx.memory().data.insert_persisted(id, self);
|
||||
ctx.data().insert_persisted(id, self);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@ use crate::*;
|
|||
#[derive(Clone, Copy, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub(crate) struct State {
|
||||
pub struct State {
|
||||
/// Positive offset means scrolling down/right
|
||||
offset: Vec2,
|
||||
pub offset: Vec2,
|
||||
|
||||
show_scroll: [bool; 2],
|
||||
|
||||
|
@ -43,14 +43,28 @@ impl Default for State {
|
|||
|
||||
impl State {
|
||||
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
|
||||
ctx.memory().data.get_persisted(id)
|
||||
ctx.data().get_persisted(id)
|
||||
}
|
||||
|
||||
pub fn store(self, ctx: &Context, id: Id) {
|
||||
ctx.memory().data.insert_persisted(id, self);
|
||||
ctx.data().insert_persisted(id, self);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScrollAreaOutput<R> {
|
||||
/// What the user closure returned.
|
||||
pub inner: R,
|
||||
|
||||
/// `Id` of the `ScrollArea`.
|
||||
pub id: Id,
|
||||
|
||||
/// The current state of the scroll area.
|
||||
pub state: State,
|
||||
|
||||
/// Where on the screen the content is (excludes scroll bars).
|
||||
pub inner_rect: Rect,
|
||||
}
|
||||
|
||||
/// Add vertical and/or horizontal scrolling to a contained [`Ui`].
|
||||
///
|
||||
/// ```
|
||||
|
@ -258,6 +272,7 @@ struct Prepared {
|
|||
/// width of the vertical bar, and the height of the horizontal bar?
|
||||
current_bar_use: Vec2,
|
||||
always_show_scroll: bool,
|
||||
/// Where on the screen the content is (excludes scroll bars).
|
||||
inner_rect: Rect,
|
||||
content_ui: Ui,
|
||||
/// Relative coordinates: the offset and size of the view of the inner UI.
|
||||
|
@ -365,7 +380,11 @@ impl ScrollArea {
|
|||
/// Show the `ScrollArea`, and add the contents to the viewport.
|
||||
///
|
||||
/// If the inner area can be very long, consider using [`Self::show_rows`] instead.
|
||||
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
|
||||
pub fn show<R>(
|
||||
self,
|
||||
ui: &mut Ui,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> ScrollAreaOutput<R> {
|
||||
self.show_viewport_dyn(ui, Box::new(|ui, _viewport| add_contents(ui)))
|
||||
}
|
||||
|
||||
|
@ -374,7 +393,7 @@ impl ScrollArea {
|
|||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// let text_style = egui::TextStyle::Body;
|
||||
/// let row_height = ui.fonts()[text_style].row_height();
|
||||
/// let row_height = ui.text_style_height(&text_style);
|
||||
/// // let row_height = ui.spacing().interact_size.y; // if you are adding buttons instead of labels.
|
||||
/// let total_rows = 10_000;
|
||||
/// egui::ScrollArea::vertical().show_rows(ui, row_height, total_rows, |ui, row_range| {
|
||||
|
@ -391,7 +410,7 @@ impl ScrollArea {
|
|||
row_height_sans_spacing: f32,
|
||||
total_rows: usize,
|
||||
add_contents: impl FnOnce(&mut Ui, std::ops::Range<usize>) -> R,
|
||||
) -> R {
|
||||
) -> ScrollAreaOutput<R> {
|
||||
let spacing = ui.spacing().item_spacing;
|
||||
let row_height_with_spacing = row_height_sans_spacing + spacing.y;
|
||||
self.show_viewport(ui, |ui, viewport| {
|
||||
|
@ -420,7 +439,11 @@ impl ScrollArea {
|
|||
///
|
||||
/// `add_contents` is past the viewport, which is the relative view of the content.
|
||||
/// So if the passed rect has min = zero, then show the top left content (the user has not scrolled).
|
||||
pub fn show_viewport<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui, Rect) -> R) -> R {
|
||||
pub fn show_viewport<R>(
|
||||
self,
|
||||
ui: &mut Ui,
|
||||
add_contents: impl FnOnce(&mut Ui, Rect) -> R,
|
||||
) -> ScrollAreaOutput<R> {
|
||||
self.show_viewport_dyn(ui, Box::new(add_contents))
|
||||
}
|
||||
|
||||
|
@ -428,16 +451,23 @@ impl ScrollArea {
|
|||
self,
|
||||
ui: &mut Ui,
|
||||
add_contents: Box<dyn FnOnce(&mut Ui, Rect) -> R + 'c>,
|
||||
) -> R {
|
||||
) -> ScrollAreaOutput<R> {
|
||||
let mut prepared = self.begin(ui);
|
||||
let ret = add_contents(&mut prepared.content_ui, prepared.viewport);
|
||||
prepared.end(ui);
|
||||
ret
|
||||
let id = prepared.id;
|
||||
let inner_rect = prepared.inner_rect;
|
||||
let inner = add_contents(&mut prepared.content_ui, prepared.viewport);
|
||||
let state = prepared.end(ui);
|
||||
ScrollAreaOutput {
|
||||
inner,
|
||||
id,
|
||||
state,
|
||||
inner_rect,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Prepared {
|
||||
fn end(self, ui: &mut Ui) {
|
||||
fn end(self, ui: &mut Ui) -> State {
|
||||
let Prepared {
|
||||
id,
|
||||
mut state,
|
||||
|
@ -523,12 +553,11 @@ impl Prepared {
|
|||
};
|
||||
let content_response = ui.interact(inner_rect, id.with("area"), sense);
|
||||
|
||||
let input = ui.input();
|
||||
if content_response.dragged() {
|
||||
for d in 0..2 {
|
||||
if has_bar[d] {
|
||||
state.offset[d] -= input.pointer.delta()[d];
|
||||
state.vel[d] = input.pointer.velocity()[d];
|
||||
state.offset[d] -= ui.input().pointer.delta()[d];
|
||||
state.vel[d] = ui.input().pointer.velocity()[d];
|
||||
state.scroll_stuck_to_end[d] = false;
|
||||
} else {
|
||||
state.vel[d] = 0.0;
|
||||
|
@ -537,7 +566,7 @@ impl Prepared {
|
|||
} else {
|
||||
let stop_speed = 20.0; // Pixels per second.
|
||||
let friction_coeff = 1000.0; // Pixels per second squared.
|
||||
let dt = input.unstable_dt;
|
||||
let dt = ui.input().unstable_dt;
|
||||
|
||||
let friction = friction_coeff * dt;
|
||||
if friction > state.vel.length() || state.vel.length() < stop_speed {
|
||||
|
@ -711,13 +740,13 @@ impl Prepared {
|
|||
|
||||
ui.painter().add(epaint::Shape::rect_filled(
|
||||
outer_scroll_rect,
|
||||
visuals.corner_radius,
|
||||
visuals.rounding,
|
||||
ui.visuals().extreme_bg_color,
|
||||
));
|
||||
|
||||
ui.painter().add(epaint::Shape::rect_filled(
|
||||
handle_rect,
|
||||
visuals.corner_radius,
|
||||
visuals.rounding,
|
||||
visuals.bg_fill,
|
||||
));
|
||||
}
|
||||
|
@ -748,6 +777,8 @@ impl Prepared {
|
|||
state.show_scroll = show_scroll_this_frame;
|
||||
|
||||
state.store(ui.ctx(), id);
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -235,7 +235,7 @@ impl<'open> Window<'open> {
|
|||
#[inline]
|
||||
pub fn show<R>(
|
||||
self,
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> Option<InnerResponse<Option<R>>> {
|
||||
self.show_dyn(ctx, Box::new(add_contents))
|
||||
|
@ -243,7 +243,7 @@ impl<'open> Window<'open> {
|
|||
|
||||
fn show_dyn<'c, R>(
|
||||
self,
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
) -> Option<InnerResponse<Option<R>>> {
|
||||
let Window {
|
||||
|
@ -296,7 +296,8 @@ impl<'open> Window<'open> {
|
|||
.and_then(|window_interaction| {
|
||||
// Calculate roughly how much larger the window size is compared to the inner rect
|
||||
let title_bar_height = if with_title_bar {
|
||||
title.font_height(ctx.fonts(), &ctx.style()) + title_content_spacing
|
||||
let style = ctx.style();
|
||||
title.font_height(&ctx.fonts(), &style) + title_content_spacing
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
@ -353,7 +354,7 @@ impl<'open> Window<'open> {
|
|||
}
|
||||
|
||||
if scroll.has_any_bar() {
|
||||
scroll.show(ui, add_contents)
|
||||
scroll.show(ui, add_contents).inner
|
||||
} else {
|
||||
add_contents(ui)
|
||||
}
|
||||
|
@ -698,42 +699,62 @@ fn paint_frame_interaction(
|
|||
) {
|
||||
use epaint::tessellator::path::add_circle_quadrant;
|
||||
|
||||
let cr = ui.visuals().window_corner_radius;
|
||||
let rounding = ui.visuals().window_rounding;
|
||||
let Rect { min, max } = rect;
|
||||
|
||||
let mut points = Vec::new();
|
||||
|
||||
if interaction.right && !interaction.bottom && !interaction.top {
|
||||
points.push(pos2(max.x, min.y + cr));
|
||||
points.push(pos2(max.x, max.y - cr));
|
||||
points.push(pos2(max.x, min.y + rounding.ne));
|
||||
points.push(pos2(max.x, max.y - rounding.se));
|
||||
}
|
||||
if interaction.right && interaction.bottom {
|
||||
points.push(pos2(max.x, min.y + cr));
|
||||
points.push(pos2(max.x, max.y - cr));
|
||||
add_circle_quadrant(&mut points, pos2(max.x - cr, max.y - cr), cr, 0.0);
|
||||
points.push(pos2(max.x, min.y + rounding.ne));
|
||||
points.push(pos2(max.x, max.y - rounding.se));
|
||||
add_circle_quadrant(
|
||||
&mut points,
|
||||
pos2(max.x - rounding.se, max.y - rounding.se),
|
||||
rounding.se,
|
||||
0.0,
|
||||
);
|
||||
}
|
||||
if interaction.bottom {
|
||||
points.push(pos2(max.x - cr, max.y));
|
||||
points.push(pos2(min.x + cr, max.y));
|
||||
points.push(pos2(max.x - rounding.se, max.y));
|
||||
points.push(pos2(min.x + rounding.sw, max.y));
|
||||
}
|
||||
if interaction.left && interaction.bottom {
|
||||
add_circle_quadrant(&mut points, pos2(min.x + cr, max.y - cr), cr, 1.0);
|
||||
add_circle_quadrant(
|
||||
&mut points,
|
||||
pos2(min.x + rounding.sw, max.y - rounding.sw),
|
||||
rounding.sw,
|
||||
1.0,
|
||||
);
|
||||
}
|
||||
if interaction.left {
|
||||
points.push(pos2(min.x, max.y - cr));
|
||||
points.push(pos2(min.x, min.y + cr));
|
||||
points.push(pos2(min.x, max.y - rounding.sw));
|
||||
points.push(pos2(min.x, min.y + rounding.nw));
|
||||
}
|
||||
if interaction.left && interaction.top {
|
||||
add_circle_quadrant(&mut points, pos2(min.x + cr, min.y + cr), cr, 2.0);
|
||||
add_circle_quadrant(
|
||||
&mut points,
|
||||
pos2(min.x + rounding.nw, min.y + rounding.nw),
|
||||
rounding.nw,
|
||||
2.0,
|
||||
);
|
||||
}
|
||||
if interaction.top {
|
||||
points.push(pos2(min.x + cr, min.y));
|
||||
points.push(pos2(max.x - cr, min.y));
|
||||
points.push(pos2(min.x + rounding.nw, min.y));
|
||||
points.push(pos2(max.x - rounding.ne, min.y));
|
||||
}
|
||||
if interaction.right && interaction.top {
|
||||
add_circle_quadrant(&mut points, pos2(max.x - cr, min.y + cr), cr, 3.0);
|
||||
points.push(pos2(max.x, min.y + cr));
|
||||
points.push(pos2(max.x, max.y - cr));
|
||||
add_circle_quadrant(
|
||||
&mut points,
|
||||
pos2(max.x - rounding.ne, min.y + rounding.ne),
|
||||
rounding.ne,
|
||||
3.0,
|
||||
);
|
||||
points.push(pos2(max.x, min.y + rounding.ne));
|
||||
points.push(pos2(max.x, max.y - rounding.se));
|
||||
}
|
||||
ui.painter().add(Shape::line(points, visuals.bg_stroke));
|
||||
}
|
||||
|
@ -741,9 +762,16 @@ fn paint_frame_interaction(
|
|||
// ----------------------------------------------------------------------------
|
||||
|
||||
struct TitleBar {
|
||||
/// A title Id used for dragging windows
|
||||
id: Id,
|
||||
/// Prepared text in the title
|
||||
title_galley: WidgetTextGalley,
|
||||
/// Size of the title bar in a collapsed state (if window is collapsible),
|
||||
/// which includes all necessary space for showing the expand button, the
|
||||
/// title and the close button.
|
||||
min_rect: Rect,
|
||||
/// Size of the title bar in an expanded state. This size become known only
|
||||
/// after expanding window and painting its content
|
||||
rect: Rect,
|
||||
}
|
||||
|
||||
|
@ -757,7 +785,7 @@ fn show_title_bar(
|
|||
) -> TitleBar {
|
||||
let inner_response = ui.horizontal(|ui| {
|
||||
let height = title
|
||||
.font_height(ui.fonts(), ui.style())
|
||||
.font_height(&ui.fonts(), ui.style())
|
||||
.max(ui.spacing().interact_size.y);
|
||||
ui.set_min_height(height);
|
||||
|
||||
|
@ -775,7 +803,7 @@ fn show_title_bar(
|
|||
collapsing.toggle(ui);
|
||||
}
|
||||
let openness = collapsing.openness(ui.ctx(), collapsing_id);
|
||||
collapsing_header::paint_icon(ui, openness, &collapse_button_response);
|
||||
collapsing_header::paint_default_icon(ui, openness, &collapse_button_response);
|
||||
}
|
||||
|
||||
let title_galley = title.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Heading);
|
||||
|
@ -804,6 +832,20 @@ fn show_title_bar(
|
|||
}
|
||||
|
||||
impl TitleBar {
|
||||
/// Finishes painting of the title bar when the window content size already known.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `ui`:
|
||||
/// - `outer_rect`:
|
||||
/// - `content_response`: if `None`, window is collapsed at this frame, otherwise contains
|
||||
/// a result of rendering the window content
|
||||
/// - `open`: if `None`, no "Close" button will be rendered, otherwise renders and processes
|
||||
/// the "Close" button and writes a `false` if window was closed
|
||||
/// - `collapsing`: holds the current expanding state. Can be changed by double click on the
|
||||
/// title if `collapsible` is `true`
|
||||
/// - `collapsible`: if `true`, double click on the title bar will be handled for a change
|
||||
/// of `collapsing` state
|
||||
fn ui(
|
||||
mut self,
|
||||
ui: &mut Ui,
|
||||
|
@ -857,6 +899,11 @@ impl TitleBar {
|
|||
}
|
||||
}
|
||||
|
||||
/// Paints the "Close" button at the right side of the title bar
|
||||
/// and processes clicks on it.
|
||||
///
|
||||
/// The button is square and its size is determined by the
|
||||
/// [`crate::style::Spacing::icon_width`] setting.
|
||||
fn close_button_ui(&self, ui: &mut Ui) -> Response {
|
||||
let button_size = Vec2::splat(ui.spacing().icon_width);
|
||||
let pad = (self.rect.height() - button_size.y) / 2.0; // calculated so that the icon is on the diagonal (if window padding is symmetrical)
|
||||
|
@ -872,6 +919,16 @@ impl TitleBar {
|
|||
}
|
||||
}
|
||||
|
||||
/// Paints the "Close" button of the window and processes clicks on it.
|
||||
///
|
||||
/// The close button is just an `X` symbol painted by a current stroke
|
||||
/// for foreground elements (such as a label text).
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `ui`:
|
||||
/// - `rect`: The rectangular area to fit the button in
|
||||
///
|
||||
/// Returns the result of a click on a button if it was pressed
|
||||
fn close_button(ui: &mut Ui, rect: Rect) -> Response {
|
||||
let close_id = ui.auto_id_with("window_close_button");
|
||||
let response = ui.interact(rect, close_id, Sense::click());
|
||||
|
@ -880,9 +937,9 @@ fn close_button(ui: &mut Ui, rect: Rect) -> Response {
|
|||
let visuals = ui.style().interact(&response);
|
||||
let rect = rect.shrink(2.0).expand(visuals.expansion);
|
||||
let stroke = visuals.fg_stroke;
|
||||
ui.painter()
|
||||
ui.painter() // paints \
|
||||
.line_segment([rect.left_top(), rect.right_bottom()], stroke);
|
||||
ui.painter()
|
||||
ui.painter() // paints /
|
||||
.line_segment([rect.right_top(), rect.left_bottom()], stroke);
|
||||
response
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -28,6 +28,13 @@ pub struct RawInput {
|
|||
/// Set this the first frame, whenever it changes, or just on every frame.
|
||||
pub pixels_per_point: Option<f32>,
|
||||
|
||||
/// Maximum size of one side of the font texture.
|
||||
///
|
||||
/// Ask your graphics drivers about this. This corresponds to `GL_MAX_TEXTURE_SIZE`.
|
||||
///
|
||||
/// The default is a very small (but very portable) 2048.
|
||||
pub max_texture_side: usize,
|
||||
|
||||
/// Monotonically increasing time, in seconds. Relative to whatever. Used for animations.
|
||||
/// If `None` is provided, egui will assume a time delta of `predicted_dt` (default 1/60 seconds).
|
||||
pub time: Option<f64>,
|
||||
|
@ -62,6 +69,7 @@ impl Default for RawInput {
|
|||
Self {
|
||||
screen_rect: None,
|
||||
pixels_per_point: None,
|
||||
max_texture_side: 2048,
|
||||
time: None,
|
||||
predicted_dt: 1.0 / 60.0,
|
||||
modifiers: Modifiers::default(),
|
||||
|
@ -81,6 +89,7 @@ impl RawInput {
|
|||
RawInput {
|
||||
screen_rect: self.screen_rect.take(),
|
||||
pixels_per_point: self.pixels_per_point.take(),
|
||||
max_texture_side: self.max_texture_side,
|
||||
time: self.time.take(),
|
||||
predicted_dt: self.predicted_dt,
|
||||
modifiers: self.modifiers,
|
||||
|
@ -95,6 +104,7 @@ impl RawInput {
|
|||
let Self {
|
||||
screen_rect,
|
||||
pixels_per_point,
|
||||
max_texture_side,
|
||||
time,
|
||||
predicted_dt,
|
||||
modifiers,
|
||||
|
@ -105,6 +115,7 @@ impl RawInput {
|
|||
|
||||
self.screen_rect = screen_rect.or(self.screen_rect);
|
||||
self.pixels_per_point = pixels_per_point.or(self.pixels_per_point);
|
||||
self.max_texture_side = max_texture_side; // use latest
|
||||
self.time = time; // use latest time
|
||||
self.predicted_dt = predicted_dt; // use latest dt
|
||||
self.modifiers = modifiers; // use latest
|
||||
|
@ -135,7 +146,7 @@ pub struct DroppedFile {
|
|||
/// Set by the `egui_web` backend.
|
||||
pub last_modified: Option<std::time::SystemTime>,
|
||||
/// Set by the `egui_web` backend.
|
||||
pub bytes: Option<std::sync::Arc<[u8]>>,
|
||||
pub bytes: Option<epaint::mutex::Arc<[u8]>>,
|
||||
}
|
||||
|
||||
/// An input event generated by the integration.
|
||||
|
@ -148,7 +159,9 @@ pub enum Event {
|
|||
Copy,
|
||||
/// The integration detected a "cut" event (e.g. Cmd+X).
|
||||
Cut,
|
||||
/// Text input, e.g. via keyboard or paste action.
|
||||
/// The integration detected a "paste" event (e.g. Cmd+V).
|
||||
Paste(String),
|
||||
/// Text input, e.g. via keyboard.
|
||||
///
|
||||
/// When the user presses enter/return, do not send a `Text` (just [`Key::Enter`]).
|
||||
Text(String),
|
||||
|
@ -178,6 +191,8 @@ pub enum Event {
|
|||
/// The direction of the vector indicates how to move the _content_ that is being viewed.
|
||||
/// So if you get positive values, the content being viewed should move to the right and down,
|
||||
/// revealing new things to the left and up.
|
||||
///
|
||||
/// Shift-scroll should result in horizontal scrolling (it is up to the integrations to do this).
|
||||
Scroll(Vec2),
|
||||
|
||||
/// Zoom scale factor this frame (e.g. from ctrl-scroll or pinch gesture).
|
||||
|
@ -353,6 +368,7 @@ impl RawInput {
|
|||
let Self {
|
||||
screen_rect,
|
||||
pixels_per_point,
|
||||
max_texture_side,
|
||||
time,
|
||||
predicted_dt,
|
||||
modifiers,
|
||||
|
@ -366,6 +382,7 @@ impl RawInput {
|
|||
.on_hover_text(
|
||||
"Also called HDPI factor.\nNumber of physical pixels per each logical pixel.",
|
||||
);
|
||||
ui.label(format!("max_texture_side: {}", max_texture_side));
|
||||
if let Some(time) = time {
|
||||
ui.label(format!("time: {:.3} s", time));
|
||||
} else {
|
||||
|
@ -373,10 +390,13 @@ impl RawInput {
|
|||
}
|
||||
ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
|
||||
ui.label(format!("modifiers: {:#?}", modifiers));
|
||||
ui.label(format!("events: {:?}", events))
|
||||
.on_hover_text("key presses etc");
|
||||
ui.label(format!("hovered_files: {}", hovered_files.len()));
|
||||
ui.label(format!("dropped_files: {}", dropped_files.len()));
|
||||
ui.scope(|ui| {
|
||||
ui.set_min_height(150.0);
|
||||
ui.label(format!("events: {:#?}", events))
|
||||
.on_hover_text("key presses etc");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,9 @@ pub struct Output {
|
|||
|
||||
/// Screen-space position of text edit cursor (used for IME).
|
||||
pub text_cursor_pos: Option<crate::Pos2>,
|
||||
|
||||
/// Texture changes since last frame.
|
||||
pub textures_delta: epaint::textures::TexturesDelta,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
|
@ -71,6 +74,7 @@ impl Output {
|
|||
mut events,
|
||||
mutable_text_under_cursor,
|
||||
text_cursor_pos,
|
||||
textures_delta,
|
||||
} = newer;
|
||||
|
||||
self.cursor_icon = cursor_icon;
|
||||
|
@ -84,6 +88,7 @@ impl Output {
|
|||
self.events.append(&mut events);
|
||||
self.mutable_text_under_cursor = mutable_text_under_cursor;
|
||||
self.text_cursor_pos = text_cursor_pos.or(self.text_cursor_pos);
|
||||
self.textures_delta.append(textures_delta);
|
||||
}
|
||||
|
||||
/// Take everything ephemeral (everything except `cursor_icon` currently)
|
||||
|
|
|
@ -72,7 +72,7 @@ impl FrameState {
|
|||
pub(crate) fn available_rect(&self) -> Rect {
|
||||
crate::egui_assert!(
|
||||
self.available_rect.is_finite(),
|
||||
"Called `available_rect()` before `CtxRef::run()`"
|
||||
"Called `available_rect()` before `Context::run()`"
|
||||
);
|
||||
self.available_rect
|
||||
}
|
||||
|
|
|
@ -9,11 +9,11 @@ pub(crate) struct State {
|
|||
|
||||
impl State {
|
||||
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
|
||||
ctx.memory().data.get_persisted(id)
|
||||
ctx.data().get_persisted(id)
|
||||
}
|
||||
|
||||
pub fn store(self, ctx: &Context, id: Id) {
|
||||
ctx.memory().data.insert_persisted(id, self);
|
||||
ctx.data().insert_persisted(id, self);
|
||||
}
|
||||
|
||||
fn set_min_col_width(&mut self, col: usize, width: f32) {
|
||||
|
@ -45,8 +45,8 @@ impl State {
|
|||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub(crate) struct GridLayout {
|
||||
ctx: CtxRef,
|
||||
style: std::sync::Arc<Style>,
|
||||
ctx: Context,
|
||||
style: epaint::mutex::Arc<Style>,
|
||||
id: Id,
|
||||
|
||||
/// State previous frame (if any).
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
/// moved outside the slider.
|
||||
///
|
||||
/// For some widgets `Id`s are also used to persist some state about the
|
||||
/// widgets, such as Window position or wether not a collapsing header region is open.
|
||||
/// widgets, such as Window position or whether not a collapsing header region is open.
|
||||
///
|
||||
/// This implies that the `Id`s must be unique.
|
||||
///
|
||||
|
@ -19,7 +19,7 @@
|
|||
///
|
||||
/// For things that need to persist state even after moving (windows, collapsing headers)
|
||||
/// the location of the widgets is obviously not good enough. For instance,
|
||||
/// a collapsing region needs to remember wether or not it is open even
|
||||
/// a collapsing region needs to remember whether or not it is open even
|
||||
/// if the layout next frame is different and the collapsing is not lower down
|
||||
/// on the screen.
|
||||
///
|
||||
|
|
|
@ -261,7 +261,8 @@ impl InputState {
|
|||
/// # egui::__run_test_ui(|ui| {
|
||||
/// let mut zoom = 1.0; // no zoom
|
||||
/// let mut rotation = 0.0; // no rotation
|
||||
/// if let Some(multi_touch) = ui.input().multi_touch() {
|
||||
/// let multi_touch = ui.input().multi_touch();
|
||||
/// if let Some(multi_touch) = multi_touch {
|
||||
/// zoom *= multi_touch.zoom_delta;
|
||||
/// rotation += multi_touch.rotation_delta;
|
||||
/// }
|
||||
|
@ -699,7 +700,12 @@ impl InputState {
|
|||
events,
|
||||
} = self;
|
||||
|
||||
ui.style_mut().body_text_style = epaint::TextStyle::Monospace;
|
||||
ui.style_mut()
|
||||
.text_styles
|
||||
.get_mut(&crate::TextStyle::Body)
|
||||
.unwrap()
|
||||
.family = crate::FontFamily::Monospace;
|
||||
|
||||
ui.collapsing("Raw Input", |ui| raw.ui(ui));
|
||||
|
||||
crate::containers::CollapsingHeader::new("🖱 Pointer")
|
||||
|
@ -729,8 +735,11 @@ impl InputState {
|
|||
ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
|
||||
ui.label(format!("modifiers: {:#?}", modifiers));
|
||||
ui.label(format!("keys_down: {:?}", keys_down));
|
||||
ui.label(format!("events: {:?}", events))
|
||||
.on_hover_text("key presses etc");
|
||||
ui.scope(|ui| {
|
||||
ui.set_min_height(150.0);
|
||||
ui.label(format!("events: {:#?}", events))
|
||||
.on_hover_text("key presses etc");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::{
|
|||
};
|
||||
|
||||
/// All you probably need to know about a multi-touch gesture.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct MultiTouchInfo {
|
||||
/// Point in time when the gesture started.
|
||||
pub start_time: f64,
|
||||
|
|
|
@ -1,75 +1,79 @@
|
|||
//! uis for egui types.
|
||||
//! Showing UI:s for egui/epaint types.
|
||||
use crate::*;
|
||||
|
||||
impl Widget for &epaint::FontImage {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
use epaint::Mesh;
|
||||
|
||||
ui.vertical(|ui| {
|
||||
// Show font texture in demo Ui
|
||||
ui.label(format!(
|
||||
"Texture size: {} x {} (hover to zoom)",
|
||||
self.width, self.height
|
||||
));
|
||||
if self.width <= 1 || self.height <= 1 {
|
||||
return;
|
||||
}
|
||||
let mut size = vec2(self.width as f32, self.height as f32);
|
||||
if size.x > ui.available_width() {
|
||||
size *= ui.available_width() / size.x;
|
||||
}
|
||||
let (rect, response) = ui.allocate_at_least(size, Sense::hover());
|
||||
let mut mesh = Mesh::default();
|
||||
mesh.add_rect_with_uv(
|
||||
rect,
|
||||
[pos2(0.0, 0.0), pos2(1.0, 1.0)].into(),
|
||||
Color32::WHITE,
|
||||
);
|
||||
ui.painter().add(Shape::mesh(mesh));
|
||||
|
||||
let (tex_w, tex_h) = (self.width as f32, self.height as f32);
|
||||
|
||||
response
|
||||
.on_hover_cursor(CursorIcon::ZoomIn)
|
||||
.on_hover_ui_at_pointer(|ui| {
|
||||
if let Some(pos) = ui.input().pointer.latest_pos() {
|
||||
let (_id, zoom_rect) = ui.allocate_space(vec2(128.0, 128.0));
|
||||
let u = remap_clamp(pos.x, rect.x_range(), 0.0..=tex_w);
|
||||
let v = remap_clamp(pos.y, rect.y_range(), 0.0..=tex_h);
|
||||
|
||||
let texel_radius = 32.0;
|
||||
let u = u.at_least(texel_radius).at_most(tex_w - texel_radius);
|
||||
let v = v.at_least(texel_radius).at_most(tex_h - texel_radius);
|
||||
|
||||
let uv_rect = Rect::from_min_max(
|
||||
pos2((u - texel_radius) / tex_w, (v - texel_radius) / tex_h),
|
||||
pos2((u + texel_radius) / tex_w, (v + texel_radius) / tex_h),
|
||||
);
|
||||
let mut mesh = Mesh::default();
|
||||
mesh.add_rect_with_uv(zoom_rect, uv_rect, Color32::WHITE);
|
||||
ui.painter().add(Shape::mesh(mesh));
|
||||
}
|
||||
});
|
||||
})
|
||||
.response
|
||||
}
|
||||
pub fn font_family_ui(ui: &mut Ui, font_family: &mut FontFamily) {
|
||||
let families = ui.fonts().families();
|
||||
ui.horizontal(|ui| {
|
||||
for alternative in families {
|
||||
let text = alternative.to_string();
|
||||
ui.radio_value(font_family, alternative, text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl Widget for &mut epaint::text::FontDefinitions {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
ui.vertical(|ui| {
|
||||
for (text_style, (_family, size)) in self.family_and_size.iter_mut() {
|
||||
// TODO: radio button for family
|
||||
ui.add(
|
||||
Slider::new(size, 4.0..=40.0)
|
||||
.max_decimals(0)
|
||||
.text(format!("{:?}", text_style)),
|
||||
);
|
||||
}
|
||||
crate::reset_button(ui, self);
|
||||
})
|
||||
.response
|
||||
}
|
||||
pub fn font_id_ui(ui: &mut Ui, font_id: &mut FontId) {
|
||||
let families = ui.fonts().families();
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(Slider::new(&mut font_id.size, 4.0..=40.0).max_decimals(0));
|
||||
for alternative in families {
|
||||
let text = alternative.to_string();
|
||||
ui.radio_value(&mut font_id.family, alternative, text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show font texture in demo Ui
|
||||
pub(crate) fn font_texture_ui(ui: &mut Ui, [width, height]: [usize; 2]) -> Response {
|
||||
use epaint::Mesh;
|
||||
|
||||
ui.vertical(|ui| {
|
||||
let color = if ui.visuals().dark_mode {
|
||||
Color32::WHITE
|
||||
} else {
|
||||
Color32::BLACK
|
||||
};
|
||||
|
||||
ui.label(format!(
|
||||
"Texture size: {} x {} (hover to zoom)",
|
||||
width, height
|
||||
));
|
||||
if width <= 1 || height <= 1 {
|
||||
return;
|
||||
}
|
||||
let mut size = vec2(width as f32, height as f32);
|
||||
if size.x > ui.available_width() {
|
||||
size *= ui.available_width() / size.x;
|
||||
}
|
||||
let (rect, response) = ui.allocate_at_least(size, Sense::hover());
|
||||
let mut mesh = Mesh::default();
|
||||
mesh.add_rect_with_uv(rect, [pos2(0.0, 0.0), pos2(1.0, 1.0)].into(), color);
|
||||
ui.painter().add(Shape::mesh(mesh));
|
||||
|
||||
let (tex_w, tex_h) = (width as f32, height as f32);
|
||||
|
||||
response
|
||||
.on_hover_cursor(CursorIcon::ZoomIn)
|
||||
.on_hover_ui_at_pointer(|ui| {
|
||||
if let Some(pos) = ui.ctx().latest_pointer_pos() {
|
||||
let (_id, zoom_rect) = ui.allocate_space(vec2(128.0, 128.0));
|
||||
let u = remap_clamp(pos.x, rect.x_range(), 0.0..=tex_w);
|
||||
let v = remap_clamp(pos.y, rect.y_range(), 0.0..=tex_h);
|
||||
|
||||
let texel_radius = 32.0;
|
||||
let u = u.at_least(texel_radius).at_most(tex_w - texel_radius);
|
||||
let v = v.at_least(texel_radius).at_most(tex_h - texel_radius);
|
||||
|
||||
let uv_rect = Rect::from_min_max(
|
||||
pos2((u - texel_radius) / tex_w, (v - texel_radius) / tex_h),
|
||||
pos2((u + texel_radius) / tex_w, (v + texel_radius) / tex_h),
|
||||
);
|
||||
let mut mesh = Mesh::default();
|
||||
mesh.add_rect_with_uv(zoom_rect, uv_rect, color);
|
||||
ui.painter().add(Shape::mesh(mesh));
|
||||
}
|
||||
});
|
||||
})
|
||||
.response
|
||||
}
|
||||
|
||||
impl Widget for &epaint::stats::PaintStats {
|
||||
|
@ -77,11 +81,11 @@ impl Widget for &epaint::stats::PaintStats {
|
|||
ui.vertical(|ui| {
|
||||
ui.label(
|
||||
"egui generates intermediate level shapes like circles and text. \
|
||||
These are later tessellated into triangles.",
|
||||
These are later tessellated into triangles.",
|
||||
);
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.style_mut().body_text_style = TextStyle::Monospace;
|
||||
ui.style_mut().override_text_style = Some(TextStyle::Monospace);
|
||||
|
||||
let epaint::stats::PaintStats {
|
||||
shapes,
|
||||
|
@ -124,7 +128,7 @@ impl Widget for &epaint::stats::PaintStats {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn label(ui: &mut Ui, alloc_info: &epaint::stats::AllocInfo, what: &str) -> Response {
|
||||
fn label(ui: &mut Ui, alloc_info: &epaint::stats::AllocInfo, what: &str) -> Response {
|
||||
ui.add(Label::new(alloc_info.format(what)).wrap(false))
|
||||
}
|
||||
|
||||
|
@ -140,9 +144,17 @@ impl Widget for &mut epaint::TessellationOptions {
|
|||
debug_paint_clip_rects,
|
||||
debug_paint_text_rects,
|
||||
debug_ignore_clip_rects,
|
||||
bezier_tolerance,
|
||||
epsilon: _,
|
||||
} = self;
|
||||
ui.checkbox(anti_alias, "Antialias")
|
||||
.on_hover_text("Turn off for small performance gain.");
|
||||
ui.add(
|
||||
crate::widgets::Slider::new(bezier_tolerance, 0.0001..=10.0)
|
||||
.logarithmic(true)
|
||||
.show_value(true)
|
||||
.text("Spline Tolerance"),
|
||||
);
|
||||
ui.collapsing("debug", |ui| {
|
||||
ui.checkbox(
|
||||
coarse_tessellation_culling,
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
//! are sometimes painted behind or in front of other things.
|
||||
|
||||
use crate::{Id, *};
|
||||
use epaint::mutex::Mutex;
|
||||
use epaint::{ClippedShape, Shape};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Different layer categories
|
||||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
|
||||
|
@ -153,10 +151,10 @@ impl PaintList {
|
|||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct GraphicLayers([IdMap<Arc<Mutex<PaintList>>>; Order::COUNT]);
|
||||
pub(crate) struct GraphicLayers([IdMap<PaintList>; Order::COUNT]);
|
||||
|
||||
impl GraphicLayers {
|
||||
pub fn list(&mut self, layer_id: LayerId) -> &Arc<Mutex<PaintList>> {
|
||||
pub fn list(&mut self, layer_id: LayerId) -> &mut PaintList {
|
||||
self.0[layer_id.order as usize]
|
||||
.entry(layer_id.id)
|
||||
.or_default()
|
||||
|
@ -171,20 +169,20 @@ impl GraphicLayers {
|
|||
// If a layer is empty at the start of the frame
|
||||
// then nobody has added to it, and it is old and defunct.
|
||||
// Free it to save memory:
|
||||
order_map.retain(|_, list| !list.lock().is_empty());
|
||||
order_map.retain(|_, list| !list.is_empty());
|
||||
|
||||
// First do the layers part of area_order:
|
||||
for layer_id in area_order {
|
||||
if layer_id.order == order {
|
||||
if let Some(list) = order_map.get_mut(&layer_id.id) {
|
||||
all_shapes.append(&mut list.lock().0);
|
||||
all_shapes.append(&mut list.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also draw areas that are missing in `area_order`:
|
||||
for shapes in order_map.values_mut() {
|
||||
all_shapes.append(&mut shapes.lock().0);
|
||||
all_shapes.append(&mut shapes.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -77,7 +77,6 @@ impl Region {
|
|||
/// Layout direction, one of `LeftToRight`, `RightToLeft`, `TopDown`, `BottomUp`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
|
||||
pub enum Direction {
|
||||
LeftToRight,
|
||||
RightToLeft,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! `egui`: an easy-to-use GUI in pure Rust!
|
||||
//!
|
||||
//! Try the live web demo: <https://emilk.github.io/egui/index.html>. Read more about egui at <https://github.com/emilk/egui>.
|
||||
//! Try the live web demo: <https://www.egui.rs/#demo>. Read more about egui at <https://github.com/emilk/egui>.
|
||||
//!
|
||||
//! `egui` is in heavy development, with each new version having breaking changes.
|
||||
//! You need to have rust 1.56.0 or later to use `egui`.
|
||||
|
@ -8,13 +8,13 @@
|
|||
//! To quickly get started with egui, you can take a look at [`eframe_template`](https://github.com/emilk/eframe_template)
|
||||
//! which uses [`eframe`](https://docs.rs/eframe).
|
||||
//!
|
||||
//! To create a GUI using egui you first need a [`CtxRef`] (by convention referred to by `ctx`).
|
||||
//! To create a GUI using egui you first need a [`Context`] (by convention referred to by `ctx`).
|
||||
//! Then you add a [`Window`] or a [`SidePanel`] to get a [`Ui`], which is what you'll be using to add all the buttons and labels that you need.
|
||||
//!
|
||||
//!
|
||||
//! # Using egui
|
||||
//!
|
||||
//! To see what is possible to build with egui you can check out the online demo at <https://emilk.github.io/egui/#demo>.
|
||||
//! To see what is possible to build with egui you can check out the online demo at <https://www.egui.rs/#demo>.
|
||||
//!
|
||||
//! If you like the "learning by doing" approach, clone <https://github.com/emilk/eframe_template> and get started using egui right away.
|
||||
//!
|
||||
|
@ -58,7 +58,7 @@
|
|||
//!
|
||||
//! ### Quick start
|
||||
//!
|
||||
//! ``` rust
|
||||
//! ```
|
||||
//! # egui::__run_test_ui(|ui| {
|
||||
//! # let mut my_string = String::new();
|
||||
//! # let mut my_boolean = true;
|
||||
|
@ -113,7 +113,7 @@
|
|||
//! # fn handle_output(_: egui::Output) {}
|
||||
//! # fn paint(_: Vec<egui::ClippedMesh>) {}
|
||||
//! # fn gather_input() -> egui::RawInput { egui::RawInput::default() }
|
||||
//! let mut ctx = egui::CtxRef::default();
|
||||
//! let mut ctx = egui::Context::default();
|
||||
//!
|
||||
//! // Game loop:
|
||||
//! loop {
|
||||
|
@ -218,7 +218,7 @@
|
|||
//! 2. Wrap your panel contents in a [`ScrollArea`], or use [`Window::vscroll`] and [`Window::hscroll`].
|
||||
//! 3. Use a justified layout:
|
||||
//!
|
||||
//! ``` rust
|
||||
//! ```
|
||||
//! # egui::__run_test_ui(|ui| {
|
||||
//! ui.with_layout(egui::Layout::top_down_justified(egui::Align::Center), |ui| {
|
||||
//! ui.button("I am becoming wider as needed");
|
||||
|
@ -228,7 +228,7 @@
|
|||
//!
|
||||
//! 4. Fill in extra space with emptiness:
|
||||
//!
|
||||
//! ``` rust
|
||||
//! ```
|
||||
//! # egui::__run_test_ui(|ui| {
|
||||
//! ui.allocate_space(ui.available_size()); // put this LAST in your panel/window code
|
||||
//! # });
|
||||
|
@ -364,7 +364,7 @@ mod frame_state;
|
|||
pub(crate) mod grid;
|
||||
mod id;
|
||||
mod input_state;
|
||||
mod introspection;
|
||||
pub mod introspection;
|
||||
pub mod layers;
|
||||
mod layout;
|
||||
mod memory;
|
||||
|
@ -385,20 +385,22 @@ pub use epaint::emath;
|
|||
pub use emath::{lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rect, Vec2};
|
||||
pub use epaint::{
|
||||
color, mutex,
|
||||
text::{FontData, FontDefinitions, FontFamily, TextStyle},
|
||||
ClippedMesh, Color32, FontImage, Rgba, Shape, Stroke, TextureId,
|
||||
text::{FontData, FontDefinitions, FontFamily, FontId},
|
||||
textures::TexturesDelta,
|
||||
AlphaImage, ClippedMesh, Color32, ColorImage, ImageData, Rgba, Rounding, Shape, Stroke,
|
||||
TextureHandle, TextureId,
|
||||
};
|
||||
|
||||
pub mod text {
|
||||
pub use epaint::text::{
|
||||
FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob, LayoutSection, TextFormat,
|
||||
TextStyle, TAB_SIZE,
|
||||
TAB_SIZE,
|
||||
};
|
||||
}
|
||||
|
||||
pub use {
|
||||
containers::*,
|
||||
context::{Context, CtxRef},
|
||||
context::Context,
|
||||
data::{
|
||||
input::*,
|
||||
output::{self, CursorIcon, Output, WidgetInfo},
|
||||
|
@ -412,7 +414,7 @@ pub use {
|
|||
painter::Painter,
|
||||
response::{InnerResponse, Response},
|
||||
sense::Sense,
|
||||
style::{Style, Visuals},
|
||||
style::{FontSelection, Style, TextStyle, Visuals},
|
||||
text::{Galley, TextFormat},
|
||||
ui::Ui,
|
||||
widget_text::{RichText, WidgetText},
|
||||
|
@ -509,7 +511,7 @@ macro_rules! egui_assert {
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// egui supports around 1216 emojis in total.
|
||||
/// The default egui fonts supports around 1216 emojis in total.
|
||||
/// Here are some of the most useful:
|
||||
/// ∞⊗⎗⎘⎙⏏⏴⏵⏶⏷
|
||||
/// ⏩⏪⏭⏮⏸⏹⏺■▶📾🔀🔁🔃
|
||||
|
@ -524,7 +526,7 @@ macro_rules! egui_assert {
|
|||
///
|
||||
/// NOTE: In egui all emojis are monochrome!
|
||||
///
|
||||
/// You can explore them all in the Font Book in [the online demo](https://emilk.github.io/egui/).
|
||||
/// You can explore them all in the Font Book in [the online demo](https://www.egui.rs/#demo).
|
||||
///
|
||||
/// In addition, egui supports a few special emojis that are not part of the unicode standard.
|
||||
/// This module contains some of them:
|
||||
|
@ -574,8 +576,8 @@ pub enum WidgetType {
|
|||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// For use in tests; especially doctests.
|
||||
pub fn __run_test_ctx(mut run_ui: impl FnMut(&CtxRef)) {
|
||||
let mut ctx = CtxRef::default();
|
||||
pub fn __run_test_ctx(mut run_ui: impl FnMut(&Context)) {
|
||||
let ctx = Context::default();
|
||||
let _ = ctx.run(Default::default(), |ctx| {
|
||||
run_ui(ctx);
|
||||
});
|
||||
|
@ -583,7 +585,7 @@ pub fn __run_test_ctx(mut run_ui: impl FnMut(&CtxRef)) {
|
|||
|
||||
/// For use in tests; especially doctests.
|
||||
pub fn __run_test_ui(mut add_contents: impl FnMut(&mut Ui)) {
|
||||
let mut ctx = CtxRef::default();
|
||||
let ctx = Context::default();
|
||||
let _ = ctx.run(Default::default(), |ctx| {
|
||||
crate::CentralPanel::default().show(ctx, |ui| {
|
||||
add_contents(ui);
|
||||
|
|
|
@ -19,7 +19,15 @@ use crate::{area, window, Id, IdMap, InputState, LayerId, Pos2, Rect, Style};
|
|||
pub struct Memory {
|
||||
pub options: Options,
|
||||
|
||||
/// This map stores current states for all widgets with custom `Id`s.
|
||||
/// This map stores some superficial state for all widgets with custom `Id`s.
|
||||
///
|
||||
/// This includes storing if a [`crate::CollapsingHeader`] is open, how far scrolled a
|
||||
/// [`crate::ScrollArea`] is, where the cursor in a [`crate::TextEdit`] is, etc.
|
||||
///
|
||||
/// This is NOT meant to store any important data. Store that in your own structures!
|
||||
///
|
||||
/// Each read clones the data, so keep your values cheap to clone.
|
||||
/// If you want to store a lot of data you should wrap it in `Arc<Mutex<…>>` so it is cheap to clone.
|
||||
///
|
||||
/// This will be saved between different program runs if you use the `persistence` feature.
|
||||
///
|
||||
|
@ -45,7 +53,7 @@ pub struct Memory {
|
|||
/// }
|
||||
/// type CharCountCache<'a> = FrameCache<usize, CharCounter>;
|
||||
///
|
||||
/// # let mut ctx = egui::CtxRef::default();
|
||||
/// # let mut ctx = egui::Context::default();
|
||||
/// let mut memory = ctx.memory();
|
||||
/// let cache = memory.caches.cache::<CharCountCache<'_>>();
|
||||
/// assert_eq!(cache.get("hello"), 5);
|
||||
|
@ -85,13 +93,13 @@ pub struct Memory {
|
|||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Some global options that you can read and write.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct Options {
|
||||
/// The default style for new `Ui`:s.
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub(crate) style: std::sync::Arc<Style>,
|
||||
pub(crate) style: epaint::mutex::Arc<Style>,
|
||||
|
||||
/// Controls the tessellator.
|
||||
pub tessellation_options: epaint::TessellationOptions,
|
||||
|
@ -100,6 +108,25 @@ pub struct Options {
|
|||
/// but is a signal to any backend that we want the [`crate::Output::events`] read out loud.
|
||||
/// Screen readers is an experimental feature of egui, and not supported on all platforms.
|
||||
pub screen_reader: bool,
|
||||
|
||||
/// If true, the most common glyphs (ASCII) are pre-rendered to the texture atlas.
|
||||
///
|
||||
/// Only the fonts in [`Style::text_styles`] will be pre-cached.
|
||||
///
|
||||
/// This can lead to fewer texture operations, but may use up the texture atlas quicker
|
||||
/// if you are changing [`Style::text_styles`], of have a lot of text styles.
|
||||
pub preload_font_glyphs: bool,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
style: Default::default(),
|
||||
tessellation_options: Default::default(),
|
||||
screen_reader: false,
|
||||
preload_font_glyphs: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -396,7 +423,7 @@ impl Memory {
|
|||
/// Popups are things like combo-boxes, color pickers, menus etc.
|
||||
/// Only one can be be open at a time.
|
||||
impl Memory {
|
||||
pub fn is_popup_open(&mut self, popup_id: Id) -> bool {
|
||||
pub fn is_popup_open(&self, popup_id: Id) -> bool {
|
||||
self.popup == Some(popup_id) || self.everything_is_visible()
|
||||
}
|
||||
|
||||
|
@ -541,7 +568,8 @@ impl Areas {
|
|||
..
|
||||
} = self;
|
||||
|
||||
*visible_last_frame = std::mem::take(visible_current_frame);
|
||||
std::mem::swap(visible_last_frame, visible_current_frame);
|
||||
visible_current_frame.clear();
|
||||
order.sort_by_key(|layer| (layer.order, wants_to_be_on_top.contains(layer)));
|
||||
wants_to_be_on_top.clear();
|
||||
}
|
||||
|
|
|
@ -16,12 +16,11 @@
|
|||
//! ```
|
||||
|
||||
use super::{
|
||||
style::WidgetVisuals, Align, CtxRef, Id, InnerResponse, PointerState, Pos2, Rect, Response,
|
||||
style::WidgetVisuals, Align, Context, Id, InnerResponse, PointerState, Pos2, Rect, Response,
|
||||
Sense, TextStyle, Ui, Vec2,
|
||||
};
|
||||
use crate::{widgets::*, *};
|
||||
use epaint::{mutex::RwLock, Stroke};
|
||||
use std::sync::Arc;
|
||||
use epaint::{mutex::Arc, mutex::RwLock, Stroke};
|
||||
|
||||
/// What is saved between frames.
|
||||
#[derive(Clone, Default)]
|
||||
|
@ -31,14 +30,11 @@ pub(crate) struct BarState {
|
|||
|
||||
impl BarState {
|
||||
fn load(ctx: &Context, bar_id: Id) -> Self {
|
||||
ctx.memory()
|
||||
.data
|
||||
.get_temp::<Self>(bar_id)
|
||||
.unwrap_or_default()
|
||||
ctx.data().get_temp::<Self>(bar_id).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn store(self, ctx: &Context, bar_id: Id) {
|
||||
ctx.memory().data.insert_temp(bar_id, self);
|
||||
ctx.data().insert_temp(bar_id, self);
|
||||
}
|
||||
|
||||
/// Show a menu at pointer if primary-clicked response.
|
||||
|
@ -116,7 +112,7 @@ pub(crate) fn submenu_button<R>(
|
|||
|
||||
/// wrapper for the contents of every menu.
|
||||
pub(crate) fn menu_ui<'c, R>(
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
menu_id: impl std::hash::Hash,
|
||||
menu_state_arc: &Arc<RwLock<MenuState>>,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R + 'c,
|
||||
|
@ -187,33 +183,19 @@ fn stationary_menu_impl<'c, R>(
|
|||
InnerResponse::new(inner.map(|r| r.inner), button_response)
|
||||
}
|
||||
|
||||
/// Stores the state for the context menu.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ContextMenuSystem {
|
||||
root: MenuRootManager,
|
||||
}
|
||||
impl ContextMenuSystem {
|
||||
/// Show a menu at pointer if right-clicked response.
|
||||
/// Should be called from [`Context`] on a [`Response`]
|
||||
pub fn context_menu(
|
||||
&mut self,
|
||||
response: &Response,
|
||||
add_contents: impl FnOnce(&mut Ui),
|
||||
) -> Option<InnerResponse<()>> {
|
||||
MenuRoot::context_click_interaction(response, &mut self.root, response.id);
|
||||
self.root.show(response, add_contents)
|
||||
}
|
||||
}
|
||||
impl std::ops::Deref for ContextMenuSystem {
|
||||
type Target = MenuRootManager;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.root
|
||||
}
|
||||
}
|
||||
impl std::ops::DerefMut for ContextMenuSystem {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.root
|
||||
}
|
||||
/// Response to secondary clicks (right-clicks) by showing the given menu.
|
||||
pub(crate) fn context_menu(
|
||||
response: &Response,
|
||||
add_contents: impl FnOnce(&mut Ui),
|
||||
) -> Option<InnerResponse<()>> {
|
||||
let menu_id = Id::new("__egui::context_menu");
|
||||
let mut bar_state = BarState::load(&response.ctx, menu_id);
|
||||
|
||||
MenuRoot::context_click_interaction(response, &mut bar_state, response.id);
|
||||
let inner_response = bar_state.show(response, add_contents);
|
||||
|
||||
bar_state.store(&response.ctx, menu_id);
|
||||
inner_response
|
||||
}
|
||||
|
||||
/// Stores the state for the context menu.
|
||||
|
@ -429,7 +411,8 @@ impl SubMenuButton {
|
|||
let button_padding = ui.spacing().button_padding;
|
||||
let total_extra = button_padding + button_padding;
|
||||
let text_available_width = ui.available_width() - total_extra.x;
|
||||
let text_galley = text.into_galley(ui, Some(true), text_available_width, text_style);
|
||||
let text_galley =
|
||||
text.into_galley(ui, Some(true), text_available_width, text_style.clone());
|
||||
|
||||
let icon_available_width = text_available_width - text_galley.size().x;
|
||||
let icon_galley = icon.into_galley(ui, Some(true), icon_available_width, text_style);
|
||||
|
@ -455,7 +438,7 @@ impl SubMenuButton {
|
|||
|
||||
ui.painter().rect_filled(
|
||||
rect.expand(visuals.expansion),
|
||||
visuals.corner_radius,
|
||||
visuals.rounding,
|
||||
visuals.bg_fill,
|
||||
);
|
||||
|
||||
|
@ -520,7 +503,7 @@ impl MenuState {
|
|||
self.response = MenuResponse::Close;
|
||||
}
|
||||
pub fn show<R>(
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
menu_state: &Arc<RwLock<Self>>,
|
||||
id: Id,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
|
@ -529,7 +512,7 @@ impl MenuState {
|
|||
}
|
||||
fn show_submenu<R>(
|
||||
&mut self,
|
||||
ctx: &CtxRef,
|
||||
ctx: &Context,
|
||||
id: Id,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> Option<R> {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use crate::{
|
||||
emath::{Align2, Pos2, Rect, Vec2},
|
||||
layers::{LayerId, PaintList, ShapeIdx},
|
||||
Color32, CtxRef,
|
||||
Color32, Context, FontId,
|
||||
};
|
||||
use epaint::{
|
||||
mutex::Mutex,
|
||||
text::{Fonts, Galley, TextStyle},
|
||||
CircleShape, RectShape, Shape, Stroke, TextShape,
|
||||
mutex::{Arc, RwLockReadGuard, RwLockWriteGuard},
|
||||
text::{Fonts, Galley},
|
||||
CircleShape, RectShape, Rounding, Shape, Stroke, TextShape,
|
||||
};
|
||||
|
||||
/// Helper to paint shapes and text to a specific region on a specific layer.
|
||||
|
@ -15,13 +15,11 @@ use epaint::{
|
|||
#[derive(Clone)]
|
||||
pub struct Painter {
|
||||
/// Source of fonts and destination of shapes
|
||||
ctx: CtxRef,
|
||||
ctx: Context,
|
||||
|
||||
/// Where we paint
|
||||
layer_id: LayerId,
|
||||
|
||||
paint_list: std::sync::Arc<Mutex<PaintList>>,
|
||||
|
||||
/// Everything painted in this `Painter` will be clipped against this.
|
||||
/// This means nothing outside of this rectangle will be visible on screen.
|
||||
clip_rect: Rect,
|
||||
|
@ -32,33 +30,30 @@ pub struct Painter {
|
|||
}
|
||||
|
||||
impl Painter {
|
||||
pub fn new(ctx: CtxRef, layer_id: LayerId, clip_rect: Rect) -> Self {
|
||||
let paint_list = ctx.graphics().list(layer_id).clone();
|
||||
/// Create a painter to a specific layer within a certain clip rectangle.
|
||||
pub fn new(ctx: Context, layer_id: LayerId, clip_rect: Rect) -> Self {
|
||||
Self {
|
||||
ctx,
|
||||
layer_id,
|
||||
paint_list,
|
||||
clip_rect,
|
||||
fade_to_color: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Redirect where you are painting.
|
||||
#[must_use]
|
||||
pub fn with_layer_id(self, layer_id: LayerId) -> Self {
|
||||
let paint_list = self.ctx.graphics().list(layer_id).clone();
|
||||
Self {
|
||||
ctx: self.ctx,
|
||||
paint_list,
|
||||
layer_id,
|
||||
clip_rect: self.clip_rect,
|
||||
fade_to_color: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// redirect
|
||||
/// Redirect where you are painting.
|
||||
pub fn set_layer_id(&mut self, layer_id: LayerId) {
|
||||
self.layer_id = layer_id;
|
||||
self.paint_list = self.ctx.graphics().list(self.layer_id).clone();
|
||||
}
|
||||
|
||||
/// If set, colors will be modified to look like this
|
||||
|
@ -83,7 +78,6 @@ impl Painter {
|
|||
Self {
|
||||
ctx: self.ctx.clone(),
|
||||
layer_id: self.layer_id,
|
||||
paint_list: self.paint_list.clone(),
|
||||
clip_rect: rect.intersect(self.clip_rect),
|
||||
fade_to_color: self.fade_to_color,
|
||||
}
|
||||
|
@ -92,15 +86,15 @@ impl Painter {
|
|||
|
||||
/// ## Accessors etc
|
||||
impl Painter {
|
||||
/// Get a reference to the parent [`CtxRef`].
|
||||
/// Get a reference to the parent [`Context`].
|
||||
#[inline(always)]
|
||||
pub fn ctx(&self) -> &CtxRef {
|
||||
pub fn ctx(&self) -> &Context {
|
||||
&self.ctx
|
||||
}
|
||||
|
||||
/// Available fonts.
|
||||
#[inline(always)]
|
||||
pub fn fonts(&self) -> &Fonts {
|
||||
pub fn fonts(&self) -> RwLockReadGuard<'_, Fonts> {
|
||||
self.ctx.fonts()
|
||||
}
|
||||
|
||||
|
@ -145,6 +139,10 @@ impl Painter {
|
|||
|
||||
/// ## Low level
|
||||
impl Painter {
|
||||
fn paint_list(&self) -> RwLockWriteGuard<'_, PaintList> {
|
||||
RwLockWriteGuard::map(self.ctx.graphics(), |g| g.list(self.layer_id))
|
||||
}
|
||||
|
||||
fn transform_shape(&self, shape: &mut Shape) {
|
||||
if let Some(fade_to_color) = self.fade_to_color {
|
||||
tint_shape_towards(shape, fade_to_color);
|
||||
|
@ -156,11 +154,11 @@ impl Painter {
|
|||
/// NOTE: all coordinates are screen coordinates!
|
||||
pub fn add(&self, shape: impl Into<Shape>) -> ShapeIdx {
|
||||
if self.fade_to_color == Some(Color32::TRANSPARENT) {
|
||||
self.paint_list.lock().add(self.clip_rect, Shape::Noop)
|
||||
self.paint_list().add(self.clip_rect, Shape::Noop)
|
||||
} else {
|
||||
let mut shape = shape.into();
|
||||
self.transform_shape(&mut shape);
|
||||
self.paint_list.lock().add(self.clip_rect, shape)
|
||||
self.paint_list().add(self.clip_rect, shape)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,7 +176,7 @@ impl Painter {
|
|||
}
|
||||
}
|
||||
|
||||
self.paint_list.lock().extend(self.clip_rect, shapes);
|
||||
self.paint_list().extend(self.clip_rect, shapes);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,7 +187,7 @@ impl Painter {
|
|||
}
|
||||
let mut shape = shape.into();
|
||||
self.transform_shape(&mut shape);
|
||||
self.paint_list.lock().set(idx, self.clip_rect, shape);
|
||||
self.paint_list().set(idx, self.clip_rect, shape);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,12 +196,11 @@ impl Painter {
|
|||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn debug_rect(&mut self, rect: Rect, color: Color32, text: impl ToString) {
|
||||
self.rect_stroke(rect, 0.0, (1.0, color));
|
||||
let text_style = TextStyle::Monospace;
|
||||
self.text(
|
||||
rect.min,
|
||||
Align2::LEFT_TOP,
|
||||
text.to_string(),
|
||||
text_style,
|
||||
FontId::monospace(14.0),
|
||||
color,
|
||||
);
|
||||
}
|
||||
|
@ -221,7 +218,7 @@ impl Painter {
|
|||
color: Color32,
|
||||
text: impl ToString,
|
||||
) -> Rect {
|
||||
let galley = self.layout_no_wrap(text.to_string(), TextStyle::Monospace, color);
|
||||
let galley = self.layout_no_wrap(text.to_string(), FontId::monospace(14.0), color);
|
||||
let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size()));
|
||||
let frame_rect = rect.expand(2.0);
|
||||
self.add(Shape::rect_filled(
|
||||
|
@ -236,6 +233,8 @@ impl Painter {
|
|||
|
||||
/// # Paint different primitives
|
||||
impl Painter {
|
||||
/// Paints the line from the first point to the second using the `stroke`
|
||||
/// for outlining shape.
|
||||
pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into<Stroke>) {
|
||||
self.add(Shape::LineSegment {
|
||||
points,
|
||||
|
@ -279,31 +278,41 @@ impl Painter {
|
|||
pub fn rect(
|
||||
&self,
|
||||
rect: Rect,
|
||||
corner_radius: f32,
|
||||
rounding: impl Into<Rounding>,
|
||||
fill_color: impl Into<Color32>,
|
||||
stroke: impl Into<Stroke>,
|
||||
) {
|
||||
self.add(RectShape {
|
||||
rect,
|
||||
corner_radius,
|
||||
rounding: rounding.into(),
|
||||
fill: fill_color.into(),
|
||||
stroke: stroke.into(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn rect_filled(&self, rect: Rect, corner_radius: f32, fill_color: impl Into<Color32>) {
|
||||
pub fn rect_filled(
|
||||
&self,
|
||||
rect: Rect,
|
||||
rounding: impl Into<Rounding>,
|
||||
fill_color: impl Into<Color32>,
|
||||
) {
|
||||
self.add(RectShape {
|
||||
rect,
|
||||
corner_radius,
|
||||
rounding: rounding.into(),
|
||||
fill: fill_color.into(),
|
||||
stroke: Default::default(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn rect_stroke(&self, rect: Rect, corner_radius: f32, stroke: impl Into<Stroke>) {
|
||||
pub fn rect_stroke(
|
||||
&self,
|
||||
rect: Rect,
|
||||
rounding: impl Into<Rounding>,
|
||||
stroke: impl Into<Stroke>,
|
||||
) {
|
||||
self.add(RectShape {
|
||||
rect,
|
||||
corner_radius,
|
||||
rounding: rounding.into(),
|
||||
fill: Default::default(),
|
||||
stroke: stroke.into(),
|
||||
});
|
||||
|
@ -326,7 +335,7 @@ impl Painter {
|
|||
impl Painter {
|
||||
/// Lay out and paint some text.
|
||||
///
|
||||
/// To center the text at the given position, use `anchor: (Center, Center)`.
|
||||
/// To center the text at the given position, use `Align2::CENTER_CENTER`.
|
||||
///
|
||||
/// To find out the size of text before painting it, use
|
||||
/// [`Self::layout`] or [`Self::layout_no_wrap`].
|
||||
|
@ -338,10 +347,10 @@ impl Painter {
|
|||
pos: Pos2,
|
||||
anchor: Align2,
|
||||
text: impl ToString,
|
||||
text_style: TextStyle,
|
||||
font_id: FontId,
|
||||
text_color: Color32,
|
||||
) -> Rect {
|
||||
let galley = self.layout_no_wrap(text.to_string(), text_style, text_color);
|
||||
let galley = self.layout_no_wrap(text.to_string(), font_id, text_color);
|
||||
let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size()));
|
||||
self.galley(rect.min, galley);
|
||||
rect
|
||||
|
@ -354,11 +363,11 @@ impl Painter {
|
|||
pub fn layout(
|
||||
&self,
|
||||
text: String,
|
||||
text_style: TextStyle,
|
||||
font_id: FontId,
|
||||
color: crate::Color32,
|
||||
wrap_width: f32,
|
||||
) -> std::sync::Arc<Galley> {
|
||||
self.fonts().layout(text, text_style, color, wrap_width)
|
||||
) -> Arc<Galley> {
|
||||
self.fonts().layout(text, font_id, color, wrap_width)
|
||||
}
|
||||
|
||||
/// Will line break at `\n`.
|
||||
|
@ -368,10 +377,10 @@ impl Painter {
|
|||
pub fn layout_no_wrap(
|
||||
&self,
|
||||
text: String,
|
||||
text_style: TextStyle,
|
||||
font_id: FontId,
|
||||
color: crate::Color32,
|
||||
) -> std::sync::Arc<Galley> {
|
||||
self.fonts().layout(text, text_style, color, f32::INFINITY)
|
||||
) -> Arc<Galley> {
|
||||
self.fonts().layout(text, font_id, color, f32::INFINITY)
|
||||
}
|
||||
|
||||
/// Paint text that has already been layed out in a [`Galley`].
|
||||
|
@ -380,7 +389,7 @@ impl Painter {
|
|||
///
|
||||
/// If you want to change the color of the text, use [`Self::galley_with_color`].
|
||||
#[inline(always)]
|
||||
pub fn galley(&self, pos: Pos2, galley: std::sync::Arc<Galley>) {
|
||||
pub fn galley(&self, pos: Pos2, galley: Arc<Galley>) {
|
||||
if !galley.is_empty() {
|
||||
self.add(Shape::galley(pos, galley));
|
||||
}
|
||||
|
@ -392,12 +401,7 @@ impl Painter {
|
|||
///
|
||||
/// The text color in the [`Galley`] will be replaced with the given color.
|
||||
#[inline(always)]
|
||||
pub fn galley_with_color(
|
||||
&self,
|
||||
pos: Pos2,
|
||||
galley: std::sync::Arc<Galley>,
|
||||
text_color: Color32,
|
||||
) {
|
||||
pub fn galley_with_color(&self, pos: Pos2, galley: Arc<Galley>, text_color: Color32) {
|
||||
if !galley.is_empty() {
|
||||
self.add(TextShape {
|
||||
override_text_color: Some(text_color),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{
|
||||
emath::{lerp, Align, Pos2, Rect, Vec2},
|
||||
CtxRef, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, WidgetText, NUM_POINTER_BUTTONS,
|
||||
menu, Context, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, WidgetText,
|
||||
NUM_POINTER_BUTTONS,
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -16,7 +17,7 @@ use crate::{
|
|||
pub struct Response {
|
||||
// CONTEXT:
|
||||
/// Used for optionally showing a tooltip and checking for more interactions.
|
||||
pub ctx: CtxRef,
|
||||
pub ctx: Context,
|
||||
|
||||
// IN:
|
||||
/// Which layer the widget is part of.
|
||||
|
@ -382,6 +383,14 @@ impl Response {
|
|||
true
|
||||
}
|
||||
|
||||
/// Like `on_hover_text`, but show the text next to cursor.
|
||||
#[doc(alias = "tooltip")]
|
||||
pub fn on_hover_text_at_pointer(self, text: impl Into<WidgetText>) -> Self {
|
||||
self.on_hover_ui_at_pointer(|ui| {
|
||||
ui.add(crate::widgets::Label::new(text));
|
||||
})
|
||||
}
|
||||
|
||||
/// Show this text if the widget was hovered (i.e. a tooltip).
|
||||
///
|
||||
/// The text will not be visible if the widget is not enabled.
|
||||
|
@ -479,7 +488,7 @@ impl Response {
|
|||
|
||||
/// Response to secondary clicks (right-clicks) by showing the given menu.
|
||||
///
|
||||
/// ``` rust
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// let response = ui.label("Right-click me!");
|
||||
/// response.context_menu(|ui| {
|
||||
|
@ -492,9 +501,7 @@ impl Response {
|
|||
///
|
||||
/// See also: [`Ui::menu_button`] and [`Ui::close_menu`].
|
||||
pub fn context_menu(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
|
||||
self.ctx
|
||||
.context_menu_system()
|
||||
.context_menu(&self, add_contents);
|
||||
menu::context_menu(&self, add_contents);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -587,7 +594,9 @@ impl std::ops::BitOrAssign for Response {
|
|||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct InnerResponse<R> {
|
||||
/// What the user closure returned.
|
||||
pub inner: R,
|
||||
/// The response of the area.
|
||||
pub response: Response,
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,123 @@
|
|||
|
||||
#![allow(clippy::if_same_then_else)]
|
||||
|
||||
use crate::{color::*, emath::*, Response, RichText, WidgetText};
|
||||
use epaint::{Shadow, Stroke, TextStyle};
|
||||
use crate::{color::*, emath::*, FontFamily, FontId, Response, RichText, WidgetText};
|
||||
use epaint::{mutex::Arc, Rounding, Shadow, Stroke};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Alias for a [`FontId`] (font of a certain size).
|
||||
///
|
||||
/// The font is found via look-up in [`Style::text_styles`].
|
||||
/// You can use [`TextStyle::resolve`] to do this lookup.
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum TextStyle {
|
||||
/// Used when small text is needed.
|
||||
Small,
|
||||
|
||||
/// Normal labels. Easily readable, doesn't take up too much space.
|
||||
Body,
|
||||
|
||||
/// Same size as [`Self::Body]`, but used when monospace is important (for aligning number, code snippets, etc).
|
||||
Monospace,
|
||||
|
||||
/// Buttons. Maybe slightly bigger than [`Self::Body]`.
|
||||
/// Signifies that he item is interactive.
|
||||
Button,
|
||||
|
||||
/// Heading. Probably larger than [`Self::Body]`.
|
||||
Heading,
|
||||
|
||||
/// A user-chosen style, found in [`Style::text_styles`].
|
||||
/// ```
|
||||
/// egui::TextStyle::Name("footing".into());
|
||||
/// ````
|
||||
Name(Arc<str>),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TextStyle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Small => "Small".fmt(f),
|
||||
Self::Body => "Body".fmt(f),
|
||||
Self::Monospace => "Monospace".fmt(f),
|
||||
Self::Button => "Button".fmt(f),
|
||||
Self::Heading => "Heading".fmt(f),
|
||||
Self::Name(name) => (*name).fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
/// Look up this [`TextStyle`] in [`Style::text_styles`].
|
||||
pub fn resolve(&self, style: &Style) -> FontId {
|
||||
style.text_styles.get(self).cloned().unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Failed to find {:?} in Style::text_styles. Available styles:\n{:#?}",
|
||||
self,
|
||||
style.text_styles()
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// A way to select [`FontId`], either by picking one directly or by using a [`TextStyle`].
|
||||
pub enum FontSelection {
|
||||
/// Default text style - will use [`TextStyle::Body`], unless
|
||||
/// [`Style::override_font_id`] or [`Style::override_text_style`] is set.
|
||||
Default,
|
||||
|
||||
/// Directly select size and font family
|
||||
FontId(FontId),
|
||||
|
||||
/// Use a [`TextStyle`] to look up the [`FontId`] in [`Style::text_styles`].
|
||||
Style(TextStyle),
|
||||
}
|
||||
|
||||
impl Default for FontSelection {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
impl FontSelection {
|
||||
pub fn resolve(self, style: &Style) -> FontId {
|
||||
match self {
|
||||
Self::Default => {
|
||||
if let Some(override_font_id) = &style.override_font_id {
|
||||
override_font_id.clone()
|
||||
} else if let Some(text_style) = &style.override_text_style {
|
||||
text_style.resolve(style)
|
||||
} else {
|
||||
TextStyle::Body.resolve(style)
|
||||
}
|
||||
}
|
||||
Self::FontId(font_id) => font_id,
|
||||
Self::Style(text_style) => text_style.resolve(style),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FontId> for FontSelection {
|
||||
#[inline(always)]
|
||||
fn from(font_id: FontId) -> Self {
|
||||
Self::FontId(font_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TextStyle> for FontSelection {
|
||||
#[inline(always)]
|
||||
fn from(text_style: TextStyle) -> Self {
|
||||
Self::Style(text_style)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Specifies the look and feel of egui.
|
||||
///
|
||||
|
@ -15,15 +130,23 @@ use epaint::{Shadow, Stroke, TextStyle};
|
|||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct Style {
|
||||
/// Default `TextStyle` for normal text (i.e. for `Label` and `TextEdit`).
|
||||
pub body_text_style: TextStyle,
|
||||
|
||||
/// If set this will change the default [`TextStyle`] for all widgets.
|
||||
///
|
||||
/// On most widgets you can also set an explicit text style,
|
||||
/// which will take precedence over this.
|
||||
pub override_text_style: Option<TextStyle>,
|
||||
|
||||
/// If set this will change the font family and size for all widgets.
|
||||
///
|
||||
/// On most widgets you can also set an explicit text style,
|
||||
/// which will take precedence over this.
|
||||
pub override_font_id: Option<FontId>,
|
||||
|
||||
/// The [`FontFamily`] and size you want to use for a specific [`TextStyle`].
|
||||
///
|
||||
/// The most convenient way to look something up in this is to use [`TextStyle::resolve`].
|
||||
pub text_styles: BTreeMap<TextStyle, FontId>,
|
||||
|
||||
/// If set, labels buttons wtc will use this to determine whether or not
|
||||
/// to wrap the text at the right edge of the `Ui` they are in.
|
||||
/// By default this is `None`.
|
||||
|
@ -77,6 +200,11 @@ impl Style {
|
|||
pub fn noninteractive(&self) -> &WidgetVisuals {
|
||||
&self.visuals.widgets.noninteractive
|
||||
}
|
||||
|
||||
/// All known text styles.
|
||||
pub fn text_styles(&self) -> Vec<TextStyle> {
|
||||
self.text_styles.keys().cloned().collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls the sizes and distances between widgets.
|
||||
|
@ -187,10 +315,10 @@ pub struct Visuals {
|
|||
///
|
||||
/// If `text_color` is `None` (default), then the text color will be the same as the
|
||||
/// foreground stroke color (`WidgetVisuals::fg_stroke`)
|
||||
/// and will depend on wether or not the widget is being interacted with.
|
||||
/// and will depend on whether or not the widget is being interacted with.
|
||||
///
|
||||
/// In the future we may instead modulate
|
||||
/// the `text_color` based on wether or not it is interacted with
|
||||
/// the `text_color` based on whether or not it is interacted with
|
||||
/// so that `visuals.text_color` is always used,
|
||||
/// but its alpha may be different based on whether or not
|
||||
/// it is disabled, non-interactive, hovered etc.
|
||||
|
@ -216,7 +344,7 @@ pub struct Visuals {
|
|||
/// Background color behind code-styled monospaced labels.
|
||||
pub code_bg_color: Color32,
|
||||
|
||||
pub window_corner_radius: f32,
|
||||
pub window_rounding: Rounding,
|
||||
pub window_shadow: Shadow,
|
||||
|
||||
pub popup_shadow: Shadow,
|
||||
|
@ -257,6 +385,7 @@ impl Visuals {
|
|||
self.widgets.active.text_color()
|
||||
}
|
||||
|
||||
/// Window background color.
|
||||
#[inline(always)]
|
||||
pub fn window_fill(&self) -> Color32 {
|
||||
self.widgets.noninteractive.bg_fill
|
||||
|
@ -324,7 +453,7 @@ pub struct WidgetVisuals {
|
|||
pub bg_stroke: Stroke,
|
||||
|
||||
/// Button frames etc.
|
||||
pub corner_radius: f32,
|
||||
pub rounding: Rounding,
|
||||
|
||||
/// Stroke and text color of the interactive part of a component (button text, slider grab, check-mark, …).
|
||||
pub fg_stroke: Stroke,
|
||||
|
@ -355,11 +484,35 @@ pub struct DebugOptions {
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// The default text styles of the default egui theme.
|
||||
pub fn default_text_styles() -> BTreeMap<TextStyle, FontId> {
|
||||
let mut text_styles = BTreeMap::new();
|
||||
text_styles.insert(
|
||||
TextStyle::Small,
|
||||
FontId::new(10.0, FontFamily::Proportional),
|
||||
);
|
||||
text_styles.insert(TextStyle::Body, FontId::new(14.0, FontFamily::Proportional));
|
||||
text_styles.insert(
|
||||
TextStyle::Button,
|
||||
FontId::new(14.0, FontFamily::Proportional),
|
||||
);
|
||||
text_styles.insert(
|
||||
TextStyle::Heading,
|
||||
FontId::new(20.0, FontFamily::Proportional),
|
||||
);
|
||||
text_styles.insert(
|
||||
TextStyle::Monospace,
|
||||
FontId::new(14.0, FontFamily::Monospace),
|
||||
);
|
||||
text_styles
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
body_text_style: TextStyle::Body,
|
||||
override_font_id: None,
|
||||
override_text_style: None,
|
||||
text_styles: default_text_styles(),
|
||||
wrap: None,
|
||||
spacing: Spacing::default(),
|
||||
interaction: Interaction::default(),
|
||||
|
@ -413,7 +566,7 @@ impl Visuals {
|
|||
faint_bg_color: Color32::from_gray(24),
|
||||
extreme_bg_color: Color32::from_gray(10),
|
||||
code_bg_color: Color32::from_gray(64),
|
||||
window_corner_radius: 6.0,
|
||||
window_rounding: Rounding::same(6.0),
|
||||
window_shadow: Shadow::big_dark(),
|
||||
popup_shadow: Shadow::small_dark(),
|
||||
resize_corner_size: 12.0,
|
||||
|
@ -476,35 +629,35 @@ impl Widgets {
|
|||
bg_fill: Color32::from_gray(27), // window background
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // separators, indentation lines, windows outlines
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // normal text color
|
||||
corner_radius: 2.0,
|
||||
rounding: Rounding::same(2.0),
|
||||
expansion: 0.0,
|
||||
},
|
||||
inactive: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(60), // button background
|
||||
bg_stroke: Default::default(),
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // button text
|
||||
corner_radius: 2.0,
|
||||
rounding: Rounding::same(2.0),
|
||||
expansion: 0.0,
|
||||
},
|
||||
hovered: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(70),
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button
|
||||
fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
|
||||
corner_radius: 3.0,
|
||||
rounding: Rounding::same(3.0),
|
||||
expansion: 1.0,
|
||||
},
|
||||
active: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(55),
|
||||
bg_stroke: Stroke::new(1.0, Color32::WHITE),
|
||||
fg_stroke: Stroke::new(2.0, Color32::WHITE),
|
||||
corner_radius: 2.0,
|
||||
rounding: Rounding::same(2.0),
|
||||
expansion: 1.0,
|
||||
},
|
||||
open: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(27),
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
|
||||
corner_radius: 2.0,
|
||||
rounding: Rounding::same(2.0),
|
||||
expansion: 0.0,
|
||||
},
|
||||
}
|
||||
|
@ -516,35 +669,35 @@ impl Widgets {
|
|||
bg_fill: Color32::from_gray(235), // window background
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(190)), // separators, indentation lines, windows outlines
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(100)), // normal text color
|
||||
corner_radius: 2.0,
|
||||
rounding: Rounding::same(2.0),
|
||||
expansion: 0.0,
|
||||
},
|
||||
inactive: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(215), // button background
|
||||
bg_stroke: Default::default(),
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(80)), // button text
|
||||
corner_radius: 2.0,
|
||||
rounding: Rounding::same(2.0),
|
||||
expansion: 0.0,
|
||||
},
|
||||
hovered: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(210),
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), // e.g. hover over window edge or button
|
||||
fg_stroke: Stroke::new(1.5, Color32::BLACK),
|
||||
corner_radius: 3.0,
|
||||
rounding: Rounding::same(3.0),
|
||||
expansion: 1.0,
|
||||
},
|
||||
active: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(165),
|
||||
bg_stroke: Stroke::new(1.0, Color32::BLACK),
|
||||
fg_stroke: Stroke::new(2.0, Color32::BLACK),
|
||||
corner_radius: 2.0,
|
||||
rounding: Rounding::same(2.0),
|
||||
expansion: 1.0,
|
||||
},
|
||||
open: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(220),
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(160)),
|
||||
fg_stroke: Stroke::new(1.0, Color32::BLACK),
|
||||
corner_radius: 2.0,
|
||||
rounding: Rounding::same(2.0),
|
||||
expansion: 0.0,
|
||||
},
|
||||
}
|
||||
|
@ -564,8 +717,9 @@ use crate::{widgets::*, Ui};
|
|||
impl Style {
|
||||
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
||||
let Self {
|
||||
body_text_style,
|
||||
override_font_id,
|
||||
override_text_style,
|
||||
text_styles,
|
||||
wrap: _,
|
||||
spacing,
|
||||
interaction,
|
||||
|
@ -578,11 +732,14 @@ impl Style {
|
|||
visuals.light_dark_radio_buttons(ui);
|
||||
|
||||
crate::Grid::new("_options").show(ui, |ui| {
|
||||
ui.label("Default body text style:");
|
||||
ui.label("Override font id:");
|
||||
ui.horizontal(|ui| {
|
||||
for &style in &[TextStyle::Body, TextStyle::Monospace] {
|
||||
let text = crate::RichText::new(format!("{:?}", style)).text_style(style);
|
||||
ui.radio_value(body_text_style, style, text);
|
||||
ui.radio_value(override_font_id, None, "None");
|
||||
if ui.radio(override_font_id.is_some(), "override").clicked() {
|
||||
*override_font_id = Some(FontId::default());
|
||||
}
|
||||
if let Some(override_font_id) = override_font_id {
|
||||
crate::introspection::font_id_ui(ui, override_font_id);
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
|
@ -591,12 +748,14 @@ impl Style {
|
|||
crate::ComboBox::from_id_source("Override text style")
|
||||
.selected_text(match override_text_style {
|
||||
None => "None".to_owned(),
|
||||
Some(override_text_style) => format!("{:?}", override_text_style),
|
||||
Some(override_text_style) => override_text_style.to_string(),
|
||||
})
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(override_text_style, None, "None");
|
||||
for style in TextStyle::all() {
|
||||
let text = crate::RichText::new(format!("{:?}", style)).text_style(style);
|
||||
let all_text_styles = ui.style().text_styles();
|
||||
for style in all_text_styles {
|
||||
let text =
|
||||
crate::RichText::new(style.to_string()).text_style(style.clone());
|
||||
ui.selectable_value(override_text_style, Some(style), text);
|
||||
}
|
||||
});
|
||||
|
@ -611,6 +770,7 @@ impl Style {
|
|||
ui.end_row();
|
||||
});
|
||||
|
||||
ui.collapsing("🔠 Text Styles", |ui| text_styles_ui(ui, text_styles));
|
||||
ui.collapsing("📏 Spacing", |ui| spacing.ui(ui));
|
||||
ui.collapsing("☝ Interaction", |ui| interaction.ui(ui));
|
||||
ui.collapsing("🎨 Visuals", |ui| visuals.ui(ui));
|
||||
|
@ -625,6 +785,20 @@ impl Style {
|
|||
}
|
||||
}
|
||||
|
||||
fn text_styles_ui(ui: &mut Ui, text_styles: &mut BTreeMap<TextStyle, FontId>) -> Response {
|
||||
ui.vertical(|ui| {
|
||||
crate::Grid::new("text_styles").show(ui, |ui| {
|
||||
for (text_style, font_id) in text_styles.iter_mut() {
|
||||
ui.label(RichText::new(text_style.to_string()).font(font_id.clone()));
|
||||
crate::introspection::font_id_ui(ui, font_id);
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
crate::reset_button_with(ui, text_styles, default_text_styles());
|
||||
})
|
||||
.response
|
||||
}
|
||||
|
||||
impl Spacing {
|
||||
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
||||
let Self {
|
||||
|
@ -759,7 +933,7 @@ impl Selection {
|
|||
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
||||
let Self { bg_fill, stroke } = self;
|
||||
ui.label("Selectable labels");
|
||||
ui_color(ui, bg_fill, "bg_fill");
|
||||
ui_color(ui, bg_fill, "background fill");
|
||||
stroke_ui(ui, stroke, "stroke");
|
||||
}
|
||||
}
|
||||
|
@ -769,14 +943,16 @@ impl WidgetVisuals {
|
|||
let Self {
|
||||
bg_fill,
|
||||
bg_stroke,
|
||||
corner_radius,
|
||||
rounding,
|
||||
fg_stroke,
|
||||
expansion,
|
||||
} = self;
|
||||
ui_color(ui, bg_fill, "bg_fill");
|
||||
stroke_ui(ui, bg_stroke, "bg_stroke");
|
||||
ui.add(Slider::new(corner_radius, 0.0..=10.0).text("corner_radius"));
|
||||
stroke_ui(ui, fg_stroke, "fg_stroke (text)");
|
||||
ui_color(ui, bg_fill, "background fill");
|
||||
stroke_ui(ui, bg_stroke, "background stroke");
|
||||
|
||||
rounding_ui(ui, rounding);
|
||||
|
||||
stroke_ui(ui, fg_stroke, "foreground stroke (text)");
|
||||
ui.add(Slider::new(expansion, -5.0..=5.0).text("expansion"))
|
||||
.on_hover_text("make shapes this much larger");
|
||||
}
|
||||
|
@ -825,7 +1001,7 @@ impl Visuals {
|
|||
faint_bg_color,
|
||||
extreme_bg_color,
|
||||
code_bg_color,
|
||||
window_corner_radius,
|
||||
window_rounding,
|
||||
window_shadow,
|
||||
popup_shadow,
|
||||
resize_corner_size,
|
||||
|
@ -850,7 +1026,9 @@ impl Visuals {
|
|||
// Common shortcuts
|
||||
ui_color(ui, &mut widgets.noninteractive.bg_fill, "Fill");
|
||||
stroke_ui(ui, &mut widgets.noninteractive.bg_stroke, "Outline");
|
||||
ui.add(Slider::new(window_corner_radius, 0.0..=20.0).text("Rounding"));
|
||||
|
||||
rounding_ui(ui, window_rounding);
|
||||
|
||||
shadow_ui(ui, window_shadow, "Shadow");
|
||||
shadow_ui(ui, popup_shadow, "Shadow (small menus and popups)");
|
||||
});
|
||||
|
@ -940,3 +1118,29 @@ fn ui_color(ui: &mut Ui, srgba: &mut Color32, label: impl Into<WidgetText>) -> R
|
|||
})
|
||||
.response
|
||||
}
|
||||
|
||||
fn rounding_ui(ui: &mut Ui, rounding: &mut Rounding) {
|
||||
const MAX: f32 = 20.0;
|
||||
let mut same = rounding.is_same();
|
||||
ui.group(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Rounding: ");
|
||||
ui.radio_value(&mut same, true, "Same");
|
||||
ui.radio_value(&mut same, false, "Separate");
|
||||
});
|
||||
|
||||
if same {
|
||||
let mut cr = rounding.nw;
|
||||
ui.add(Slider::new(&mut cr, 0.0..=MAX));
|
||||
*rounding = Rounding::same(cr);
|
||||
} else {
|
||||
ui.add(Slider::new(&mut rounding.nw, 0.0..=MAX).text("North-West"));
|
||||
ui.add(Slider::new(&mut rounding.ne, 0.0..=MAX).text("North-East"));
|
||||
ui.add(Slider::new(&mut rounding.sw, 0.0..=MAX).text("South-West"));
|
||||
ui.add(Slider::new(&mut rounding.se, 0.0..=MAX).text("South-East"));
|
||||
if rounding.is_same() {
|
||||
rounding.se *= 1.00001;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
112
egui/src/ui.rs
112
egui/src/ui.rs
|
@ -1,12 +1,11 @@
|
|||
// #![warn(missing_docs)]
|
||||
|
||||
use epaint::mutex::RwLock;
|
||||
use epaint::mutex::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
color::*, containers::*, epaint::text::Fonts, layout::*, menu::MenuState, mutex::MutexGuard,
|
||||
placer::Placer, widgets::*, *,
|
||||
color::*, containers::*, epaint::text::Fonts, layout::*, menu::MenuState, placer::Placer,
|
||||
widgets::*, *,
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -49,7 +48,7 @@ pub struct Ui {
|
|||
/// The `Style` (visuals, spacing, etc) of this ui.
|
||||
/// Commonly many `Ui`:s share the same `Style`.
|
||||
/// The `Ui` implements copy-on-write for this.
|
||||
style: std::sync::Arc<Style>,
|
||||
style: Arc<Style>,
|
||||
|
||||
/// Handles the `Ui` size and the placement of new widgets.
|
||||
placer: Placer,
|
||||
|
@ -70,7 +69,7 @@ impl Ui {
|
|||
///
|
||||
/// Normally you would not use this directly, but instead use
|
||||
/// [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`].
|
||||
pub fn new(ctx: CtxRef, layer_id: LayerId, id: Id, max_rect: Rect, clip_rect: Rect) -> Self {
|
||||
pub fn new(ctx: Context, layer_id: LayerId, id: Id, max_rect: Rect, clip_rect: Rect) -> Self {
|
||||
let style = ctx.style();
|
||||
Ui {
|
||||
id,
|
||||
|
@ -122,7 +121,7 @@ impl Ui {
|
|||
///
|
||||
/// Note that this may be a different [`Style`] than that of [`Context::style`].
|
||||
#[inline]
|
||||
pub fn style(&self) -> &std::sync::Arc<Style> {
|
||||
pub fn style(&self) -> &Arc<Style> {
|
||||
&self.style
|
||||
}
|
||||
|
||||
|
@ -134,17 +133,17 @@ impl Ui {
|
|||
/// Example:
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// ui.style_mut().body_text_style = egui::TextStyle::Heading;
|
||||
/// ui.style_mut().override_text_style = Some(egui::TextStyle::Heading);
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn style_mut(&mut self) -> &mut Style {
|
||||
std::sync::Arc::make_mut(&mut self.style) // clone-on-write
|
||||
Arc::make_mut(&mut self.style) // clone-on-write
|
||||
}
|
||||
|
||||
/// Changes apply to this `Ui` and its subsequent children.
|
||||
///
|
||||
/// To set the visuals of all `Ui`:s, use [`Context::set_visuals`].
|
||||
pub fn set_style(&mut self, style: impl Into<std::sync::Arc<Style>>) {
|
||||
pub fn set_style(&mut self, style: impl Into<Arc<Style>>) {
|
||||
self.style = style.into();
|
||||
}
|
||||
|
||||
|
@ -195,9 +194,9 @@ impl Ui {
|
|||
&mut self.style_mut().visuals
|
||||
}
|
||||
|
||||
/// Get a reference to the parent [`CtxRef`].
|
||||
/// Get a reference to the parent [`Context`].
|
||||
#[inline]
|
||||
pub fn ctx(&self) -> &CtxRef {
|
||||
pub fn ctx(&self) -> &Context {
|
||||
self.painter.ctx()
|
||||
}
|
||||
|
||||
|
@ -313,34 +312,64 @@ impl Ui {
|
|||
self.painter().layer_id()
|
||||
}
|
||||
|
||||
/// The `Input` of the `Context` associated with the `Ui`.
|
||||
/// The [`InputState`] of the [`Context`] associated with this [`Ui`].
|
||||
/// Equivalent to `.ctx().input()`.
|
||||
///
|
||||
/// Note that this locks the [`Context`], so be careful with if-let bindings:
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// if let Some(pos) = { ui.input().pointer.hover_pos() } {
|
||||
/// // This is fine!
|
||||
/// }
|
||||
///
|
||||
/// let pos = ui.input().pointer.hover_pos();
|
||||
/// if let Some(pos) = pos {
|
||||
/// // This is also fine!
|
||||
/// }
|
||||
///
|
||||
/// if let Some(pos) = ui.input().pointer.hover_pos() {
|
||||
/// // ⚠️ Using `ui` again here will lead to a dead-lock!
|
||||
/// }
|
||||
/// # });
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn input(&self) -> &InputState {
|
||||
pub fn input(&self) -> RwLockReadGuard<'_, InputState> {
|
||||
self.ctx().input()
|
||||
}
|
||||
|
||||
/// The `Memory` of the `Context` associated with the `Ui`.
|
||||
/// The [`Memory`] of the [`Context`] associated with this ui.
|
||||
/// Equivalent to `.ctx().memory()`.
|
||||
#[inline]
|
||||
pub fn memory(&self) -> MutexGuard<'_, Memory> {
|
||||
pub fn memory(&self) -> RwLockWriteGuard<'_, Memory> {
|
||||
self.ctx().memory()
|
||||
}
|
||||
|
||||
/// The `Output` of the `Context` associated with the `Ui`.
|
||||
/// Stores superficial widget state.
|
||||
#[inline]
|
||||
pub fn data(&self) -> RwLockWriteGuard<'_, crate::util::IdTypeMap> {
|
||||
self.ctx().data()
|
||||
}
|
||||
|
||||
/// The [`Output`] of the [`Context`] associated with this ui.
|
||||
/// Equivalent to `.ctx().output()`.
|
||||
#[inline]
|
||||
pub fn output(&self) -> MutexGuard<'_, Output> {
|
||||
pub fn output(&self) -> RwLockWriteGuard<'_, Output> {
|
||||
self.ctx().output()
|
||||
}
|
||||
|
||||
/// The `Fonts` of the `Context` associated with the `Ui`.
|
||||
/// The [`Fonts`] of the [`Context`] associated with this ui.
|
||||
/// Equivalent to `.ctx().fonts()`.
|
||||
#[inline]
|
||||
pub fn fonts(&self) -> &Fonts {
|
||||
pub fn fonts(&self) -> RwLockReadGuard<'_, Fonts> {
|
||||
self.ctx().fonts()
|
||||
}
|
||||
|
||||
/// The height of text of this text style
|
||||
pub fn text_style_height(&self, style: &TextStyle) -> f32 {
|
||||
self.fonts().row_height(&style.resolve(self.style()))
|
||||
}
|
||||
|
||||
/// Screen-space rectangle for clipping what we paint in this ui.
|
||||
/// This is used, for instance, to avoid painting outside a window that is smaller than its contents.
|
||||
#[inline]
|
||||
|
@ -1055,7 +1084,7 @@ impl Ui {
|
|||
/// Add extra space before the next widget.
|
||||
///
|
||||
/// The direction is dependent on the layout.
|
||||
/// This will be in addition to the [`Spacing::item_spacing`}.
|
||||
/// This will be in addition to the [`crate::style::Spacing::item_spacing`].
|
||||
///
|
||||
/// [`Self::min_rect`] will expand to contain the space.
|
||||
#[inline]
|
||||
|
@ -1068,6 +1097,16 @@ impl Ui {
|
|||
/// Shortcut for `add(Label::new(text))`
|
||||
///
|
||||
/// See also [`Label`].
|
||||
///
|
||||
/// ### Example
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// use egui::{RichText, FontId, Color32};
|
||||
/// ui.label("Normal text");
|
||||
/// ui.label(RichText::new("Large text").font(FontId::proportional(40.0)));
|
||||
/// ui.label(RichText::new("Red text").color(Color32::RED));
|
||||
/// # });
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn label(&mut self, text: impl Into<WidgetText>) -> Response {
|
||||
Label::new(text).ui(self)
|
||||
|
@ -1238,12 +1277,12 @@ impl Ui {
|
|||
pub fn radio_value<Value: PartialEq>(
|
||||
&mut self,
|
||||
current_value: &mut Value,
|
||||
selected_value: Value,
|
||||
alternative: Value,
|
||||
text: impl Into<WidgetText>,
|
||||
) -> Response {
|
||||
let mut response = self.radio(*current_value == selected_value, text);
|
||||
let mut response = self.radio(*current_value == alternative, text);
|
||||
if response.clicked() {
|
||||
*current_value = selected_value;
|
||||
*current_value = alternative;
|
||||
response.mark_changed();
|
||||
}
|
||||
response
|
||||
|
@ -1323,9 +1362,30 @@ impl Ui {
|
|||
|
||||
/// Show an image here with the given size.
|
||||
///
|
||||
/// See also [`Image`].
|
||||
/// In order to display an image you must first acquire a [`TextureHandle`]
|
||||
/// using [`Context::load_texture`].
|
||||
///
|
||||
/// ```
|
||||
/// struct MyImage {
|
||||
/// texture: Option<egui::TextureHandle>,
|
||||
/// }
|
||||
///
|
||||
/// impl MyImage {
|
||||
/// fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
/// let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
|
||||
/// // Load the texture only once.
|
||||
/// ui.ctx().load_texture("my-image", egui::ColorImage::example())
|
||||
/// });
|
||||
///
|
||||
/// // Show the image:
|
||||
/// ui.image(texture, texture.size_vec2());
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Se also [`crate::Image`] and [`crate::ImageButton`].
|
||||
#[inline]
|
||||
pub fn image(&mut self, texture_id: TextureId, size: impl Into<Vec2>) -> Response {
|
||||
pub fn image(&mut self, texture_id: impl Into<TextureId>, size: impl Into<Vec2>) -> Response {
|
||||
Image::new(texture_id, size).ui(self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,11 +109,11 @@ where
|
|||
/// `(time, value)` pairs
|
||||
/// Time difference between values can be zero, but never negative.
|
||||
// TODO: impl IntoIter
|
||||
pub fn iter(&'_ self) -> impl Iterator<Item = (f64, T)> + '_ {
|
||||
pub fn iter(&'_ self) -> impl ExactSizeIterator<Item = (f64, T)> + '_ {
|
||||
self.values.iter().map(|(time, value)| (*time, *value))
|
||||
}
|
||||
|
||||
pub fn values(&'_ self) -> impl Iterator<Item = T> + '_ {
|
||||
pub fn values(&'_ self) -> impl ExactSizeIterator<Item = T> + '_ {
|
||||
self.values.iter().map(|(_time, value)| *value)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
// For non-serializable types, these simply return `None`.
|
||||
// This will also allow users to pick their own serialization format per type.
|
||||
|
||||
use epaint::mutex::Arc;
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -275,11 +275,12 @@ impl Element {
|
|||
fn from_ron_str<T: serde::de::DeserializeOwned>(ron: &str) -> Option<T> {
|
||||
match ron::from_str::<T>(ron) {
|
||||
Ok(value) => Some(value),
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
Err(_err) => {
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::warn!(
|
||||
"egui: Failed to deserialize {} from memory: {}, ron error: {:?}",
|
||||
std::any::type_name::<T>(),
|
||||
err,
|
||||
_err,
|
||||
ron
|
||||
);
|
||||
None
|
||||
|
@ -294,7 +295,10 @@ use crate::Id;
|
|||
// TODO: make IdTypeMap generic over the key (`Id`), and make a library of IdTypeMap.
|
||||
/// Stores values identified by an [`Id`] AND a the [`std::any::TypeId`] of the value.
|
||||
///
|
||||
/// so it maps `(Id, TypeId)` to any value you want.
|
||||
/// In other words, it maps `(Id, TypeId)` to any value you want.
|
||||
///
|
||||
/// Values are cloned when read, so keep them small and light.
|
||||
/// If you want to store something bigger, wrap them in `Arc<Mutex<…>>`.
|
||||
///
|
||||
/// Values can either be "persisted" (serializable) or "temporary" (cleared when egui is shut down).
|
||||
///
|
||||
|
@ -311,19 +315,19 @@ use crate::Id;
|
|||
/// map.insert_temp(a, 42);
|
||||
///
|
||||
/// // `b` associated with an f64 and a `&'static str`
|
||||
/// map.insert_persisted(b, 6.28);
|
||||
/// map.insert_persisted(b, 13.37);
|
||||
/// map.insert_temp(b, "Hello World".to_string());
|
||||
///
|
||||
/// // we can retrieve all four values:
|
||||
/// assert_eq!(map.get_temp::<f64>(a), Some(3.14));
|
||||
/// assert_eq!(map.get_temp::<i32>(a), Some(42));
|
||||
/// assert_eq!(map.get_temp::<f64>(b), Some(6.28));
|
||||
/// assert_eq!(map.get_temp::<f64>(b), Some(13.37));
|
||||
/// assert_eq!(map.get_temp::<String>(b), Some("Hello World".to_string()));
|
||||
///
|
||||
/// // we can retrieve them like so also:
|
||||
/// assert_eq!(map.get_persisted::<f64>(a), Some(3.14));
|
||||
/// assert_eq!(map.get_persisted::<i32>(a), Some(42));
|
||||
/// assert_eq!(map.get_persisted::<f64>(b), Some(6.28));
|
||||
/// assert_eq!(map.get_persisted::<f64>(b), Some(13.37));
|
||||
/// assert_eq!(map.get_temp::<String>(b), Some("Hello World".to_string()));
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Default)]
|
||||
|
@ -346,6 +350,8 @@ impl IdTypeMap {
|
|||
}
|
||||
|
||||
/// Read a value without trying to deserialize a persisted value.
|
||||
///
|
||||
/// The call clones the value (if found), so make sure it is cheap to clone!
|
||||
#[inline]
|
||||
pub fn get_temp<T: 'static + Clone>(&mut self, id: Id) -> Option<T> {
|
||||
let hash = hash(TypeId::of::<T>(), id);
|
||||
|
@ -356,6 +362,8 @@ impl IdTypeMap {
|
|||
}
|
||||
|
||||
/// Read a value, optionally deserializing it if available.
|
||||
///
|
||||
/// The call clones the value (if found), so make sure it is cheap to clone!
|
||||
#[inline]
|
||||
pub fn get_persisted<T: SerializableAny>(&mut self, id: Id) -> Option<T> {
|
||||
let hash = hash(TypeId::of::<T>(), id);
|
||||
|
@ -544,11 +552,11 @@ fn test_two_id_two_type() {
|
|||
let b = Id::new("b");
|
||||
|
||||
let mut map: IdTypeMap = Default::default();
|
||||
map.insert_persisted(a, 6.28);
|
||||
map.insert_persisted(a, 13.37);
|
||||
map.insert_temp(b, 42);
|
||||
assert_eq!(map.get_persisted::<f64>(a), Some(6.28));
|
||||
assert_eq!(map.get_persisted::<f64>(a), Some(13.37));
|
||||
assert_eq!(map.get_persisted::<i32>(b), Some(42));
|
||||
assert_eq!(map.get_temp::<f64>(a), Some(6.28));
|
||||
assert_eq!(map.get_temp::<f64>(a), Some(13.37));
|
||||
assert_eq!(map.get_temp::<i32>(b), Some(42));
|
||||
}
|
||||
|
||||
|
@ -565,19 +573,19 @@ fn test_two_id_x_two_types() {
|
|||
map.insert_temp(a, 42);
|
||||
|
||||
// `b` associated with an f64 and a `&'static str`
|
||||
map.insert_persisted(b, 6.28);
|
||||
map.insert_persisted(b, 13.37);
|
||||
map.insert_temp(b, "Hello World".to_string());
|
||||
|
||||
// we can retrieve all four values:
|
||||
assert_eq!(map.get_temp::<f64>(a), Some(3.14));
|
||||
assert_eq!(map.get_temp::<i32>(a), Some(42));
|
||||
assert_eq!(map.get_temp::<f64>(b), Some(6.28));
|
||||
assert_eq!(map.get_temp::<f64>(b), Some(13.37));
|
||||
assert_eq!(map.get_temp::<String>(b), Some("Hello World".to_string()));
|
||||
|
||||
// we can retrieve them like so also:
|
||||
assert_eq!(map.get_persisted::<f64>(a), Some(3.14));
|
||||
assert_eq!(map.get_persisted::<i32>(a), Some(42));
|
||||
assert_eq!(map.get_persisted::<f64>(b), Some(6.28));
|
||||
assert_eq!(map.get_persisted::<f64>(b), Some(13.37));
|
||||
assert_eq!(map.get_temp::<String>(b), Some("Hello World".to_string()));
|
||||
}
|
||||
|
||||
|
@ -586,11 +594,11 @@ fn test_one_id_two_types() {
|
|||
let id = Id::new("a");
|
||||
|
||||
let mut map: IdTypeMap = Default::default();
|
||||
map.insert_persisted(id, 6.28);
|
||||
map.insert_persisted(id, 13.37);
|
||||
map.insert_temp(id, 42);
|
||||
|
||||
assert_eq!(map.get_temp::<f64>(id), Some(6.28));
|
||||
assert_eq!(map.get_persisted::<f64>(id), Some(6.28));
|
||||
assert_eq!(map.get_temp::<f64>(id), Some(13.37));
|
||||
assert_eq!(map.get_persisted::<f64>(id), Some(13.37));
|
||||
assert_eq!(map.get_temp::<i32>(id), Some(42));
|
||||
|
||||
// ------------
|
||||
|
@ -601,8 +609,8 @@ fn test_one_id_two_types() {
|
|||
assert_eq!(map.get_temp::<i32>(id), None);
|
||||
|
||||
// Other type is still there, even though it is the same if:
|
||||
assert_eq!(map.get_temp::<f64>(id), Some(6.28));
|
||||
assert_eq!(map.get_persisted::<f64>(id), Some(6.28));
|
||||
assert_eq!(map.get_temp::<f64>(id), Some(13.37));
|
||||
assert_eq!(map.get_persisted::<f64>(id), Some(13.37));
|
||||
|
||||
// But we can still remove the last:
|
||||
map.remove::<f64>(id);
|
||||
|
|
|
@ -1,17 +1,30 @@
|
|||
use std::sync::Arc;
|
||||
use epaint::mutex::Arc;
|
||||
|
||||
use crate::{
|
||||
style::WidgetVisuals, text::LayoutJob, Align, Color32, Galley, Pos2, Style, TextStyle, Ui,
|
||||
Visuals,
|
||||
style::WidgetVisuals, text::LayoutJob, Align, Color32, FontFamily, FontSelection, Galley, Pos2,
|
||||
Style, TextStyle, Ui, Visuals,
|
||||
};
|
||||
|
||||
/// Text and optional style choices for it.
|
||||
///
|
||||
/// The style choices (font, color) are applied to the entire text.
|
||||
/// For more detailed control, use [`crate::text::LayoutJob`] instead.
|
||||
///
|
||||
/// A `RichText` can be used in most widgets and helper functions, e.g. [`Ui::label`] and [`Ui::button`].
|
||||
///
|
||||
/// ### Example
|
||||
/// ```
|
||||
/// use egui::{RichText, Color32};
|
||||
///
|
||||
/// RichText::new("Plain");
|
||||
/// RichText::new("colored").color(Color32::RED);
|
||||
/// RichText::new("Large and underlined").size(20.0).underline();
|
||||
/// ```
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
pub struct RichText {
|
||||
text: String,
|
||||
size: Option<f32>,
|
||||
family: Option<FontFamily>,
|
||||
text_style: Option<TextStyle>,
|
||||
background_color: Color32,
|
||||
text_color: Option<Color32>,
|
||||
|
@ -64,6 +77,35 @@ impl RichText {
|
|||
&self.text
|
||||
}
|
||||
|
||||
/// Select the font size (in points).
|
||||
/// This overrides the value from [`Self::text_style`].
|
||||
#[inline]
|
||||
pub fn size(mut self, size: f32) -> Self {
|
||||
self.size = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Select the font family.
|
||||
///
|
||||
/// This overrides the value from [`Self::text_style`].
|
||||
///
|
||||
/// Only the families available in [`crate::FontDefinitions::families`] may be used.
|
||||
#[inline]
|
||||
pub fn family(mut self, family: FontFamily) -> Self {
|
||||
self.family = Some(family);
|
||||
self
|
||||
}
|
||||
|
||||
/// Select the font and size.
|
||||
/// This overrides the value from [`Self::text_style`].
|
||||
#[inline]
|
||||
pub fn font(mut self, font_id: crate::FontId) -> Self {
|
||||
let crate::FontId { size, family } = font_id;
|
||||
self.size = Some(size);
|
||||
self.family = Some(family);
|
||||
self
|
||||
}
|
||||
|
||||
/// Override the [`TextStyle`].
|
||||
#[inline]
|
||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||
|
@ -170,24 +212,33 @@ impl RichText {
|
|||
}
|
||||
|
||||
/// Read the font height of the selected text style.
|
||||
pub fn font_height(&self, fonts: &epaint::text::Fonts, style: &crate::Style) -> f32 {
|
||||
let text_style = self
|
||||
.text_style
|
||||
.or(style.override_text_style)
|
||||
.unwrap_or(style.body_text_style);
|
||||
fonts.row_height(text_style)
|
||||
pub fn font_height(&self, fonts: &epaint::Fonts, style: &Style) -> f32 {
|
||||
let mut font_id = self.text_style.as_ref().map_or_else(
|
||||
|| FontSelection::Default.resolve(style),
|
||||
|text_style| text_style.resolve(style),
|
||||
);
|
||||
|
||||
if let Some(size) = self.size {
|
||||
font_id.size = size;
|
||||
}
|
||||
if let Some(family) = &self.family {
|
||||
font_id.family = family.clone();
|
||||
}
|
||||
fonts.row_height(&font_id)
|
||||
}
|
||||
|
||||
fn into_text_job(
|
||||
self,
|
||||
style: &Style,
|
||||
default_text_style: TextStyle,
|
||||
fallback_font: FontSelection,
|
||||
default_valign: Align,
|
||||
) -> WidgetTextJob {
|
||||
let text_color = self.get_text_color(&style.visuals);
|
||||
|
||||
let Self {
|
||||
text,
|
||||
size,
|
||||
family,
|
||||
text_style,
|
||||
background_color,
|
||||
text_color: _, // already used by `get_text_color`
|
||||
|
@ -204,9 +255,21 @@ impl RichText {
|
|||
let line_color = text_color.unwrap_or_else(|| style.visuals.text_color());
|
||||
let text_color = text_color.unwrap_or(crate::Color32::TEMPORARY_COLOR);
|
||||
|
||||
let text_style = text_style
|
||||
.or(style.override_text_style)
|
||||
.unwrap_or(default_text_style);
|
||||
let font_id = {
|
||||
let mut font_id = text_style
|
||||
.or_else(|| style.override_text_style.clone())
|
||||
.map_or_else(
|
||||
|| fallback_font.resolve(style),
|
||||
|text_style| text_style.resolve(style),
|
||||
);
|
||||
if let Some(size) = size {
|
||||
font_id.size = size;
|
||||
}
|
||||
if let Some(family) = family {
|
||||
font_id.family = family;
|
||||
}
|
||||
font_id
|
||||
};
|
||||
|
||||
let mut background_color = background_color;
|
||||
if code {
|
||||
|
@ -230,7 +293,7 @@ impl RichText {
|
|||
};
|
||||
|
||||
let text_format = crate::text::TextFormat {
|
||||
style: text_style,
|
||||
font_id,
|
||||
color: text_color,
|
||||
background: background_color,
|
||||
italics,
|
||||
|
@ -267,8 +330,10 @@ impl RichText {
|
|||
/// but it can be a [`RichText`] (text with color, style, etc),
|
||||
/// a [`LayoutJob`] (for when you want full control of how the text looks)
|
||||
/// or text that has already been layed out in a [`Galley`].
|
||||
#[derive(Clone)]
|
||||
pub enum WidgetText {
|
||||
RichText(RichText),
|
||||
|
||||
/// Use this [`LayoutJob`] when laying out the text.
|
||||
///
|
||||
/// Only [`LayoutJob::text`] and [`LayoutJob::sections`] are guaranteed to be respected.
|
||||
|
@ -279,6 +344,7 @@ pub enum WidgetText {
|
|||
/// If you want all parts of the `LayoutJob` respected, then convert it to a
|
||||
/// [`Galley`] and use [`Self::Galley`] instead.
|
||||
LayoutJob(LayoutJob),
|
||||
|
||||
/// Use exactly this galley when painting the text.
|
||||
Galley(Arc<Galley>),
|
||||
}
|
||||
|
@ -437,7 +503,7 @@ impl WidgetText {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn font_height(&self, fonts: &epaint::text::Fonts, style: &crate::Style) -> f32 {
|
||||
pub(crate) fn font_height(&self, fonts: &epaint::Fonts, style: &Style) -> f32 {
|
||||
match self {
|
||||
Self::RichText(text) => text.font_height(fonts, style),
|
||||
Self::LayoutJob(job) => job.font_height(fonts),
|
||||
|
@ -454,11 +520,11 @@ impl WidgetText {
|
|||
pub fn into_text_job(
|
||||
self,
|
||||
style: &Style,
|
||||
default_text_style: TextStyle,
|
||||
fallback_font: FontSelection,
|
||||
default_valign: Align,
|
||||
) -> WidgetTextJob {
|
||||
match self {
|
||||
Self::RichText(text) => text.into_text_job(style, default_text_style, default_valign),
|
||||
Self::RichText(text) => text.into_text_job(style, fallback_font, default_valign),
|
||||
Self::LayoutJob(job) => WidgetTextJob {
|
||||
job,
|
||||
job_has_color: true,
|
||||
|
@ -481,7 +547,7 @@ impl WidgetText {
|
|||
ui: &Ui,
|
||||
wrap: Option<bool>,
|
||||
available_width: f32,
|
||||
default_text_style: TextStyle,
|
||||
fallback_font: impl Into<FontSelection>,
|
||||
) -> WidgetTextGalley {
|
||||
let wrap = wrap.unwrap_or_else(|| ui.wrap_text());
|
||||
let wrap_width = if wrap { available_width } else { f32::INFINITY };
|
||||
|
@ -489,7 +555,7 @@ impl WidgetText {
|
|||
match self {
|
||||
Self::RichText(text) => {
|
||||
let valign = ui.layout().vertical_align();
|
||||
let mut text_job = text.into_text_job(ui.style(), default_text_style, valign);
|
||||
let mut text_job = text.into_text_job(ui.style(), fallback_font.into(), valign);
|
||||
text_job.job.wrap_width = wrap_width;
|
||||
WidgetTextGalley {
|
||||
galley: ui.fonts().layout_job(text_job.job),
|
||||
|
|
|
@ -180,7 +180,7 @@ impl Widget for Button {
|
|||
let stroke = stroke.unwrap_or(visuals.bg_stroke);
|
||||
ui.painter().rect(
|
||||
rect.expand(visuals.expansion),
|
||||
visuals.corner_radius,
|
||||
visuals.rounding,
|
||||
fill,
|
||||
stroke,
|
||||
);
|
||||
|
@ -265,7 +265,7 @@ impl<'a> Widget for Checkbox<'a> {
|
|||
let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
|
||||
ui.painter().add(epaint::RectShape {
|
||||
rect: big_icon_rect.expand(visuals.expansion),
|
||||
corner_radius: visuals.corner_radius,
|
||||
rounding: visuals.rounding,
|
||||
fill: visuals.bg_fill,
|
||||
stroke: visuals.bg_stroke,
|
||||
});
|
||||
|
@ -394,7 +394,7 @@ pub struct ImageButton {
|
|||
}
|
||||
|
||||
impl ImageButton {
|
||||
pub fn new(texture_id: TextureId, size: impl Into<Vec2>) -> Self {
|
||||
pub fn new(texture_id: impl Into<TextureId>, size: impl Into<Vec2>) -> Self {
|
||||
Self {
|
||||
image: widgets::Image::new(texture_id, size),
|
||||
sense: Sense::click(),
|
||||
|
@ -455,9 +455,14 @@ impl Widget for ImageButton {
|
|||
response.widget_info(|| WidgetInfo::new(WidgetType::ImageButton));
|
||||
|
||||
if ui.is_rect_visible(rect) {
|
||||
let (expansion, corner_radius, fill, stroke) = if selected {
|
||||
let (expansion, rounding, fill, stroke) = if selected {
|
||||
let selection = ui.visuals().selection;
|
||||
(-padding, 0.0, selection.bg_fill, selection.stroke)
|
||||
(
|
||||
-padding,
|
||||
Rounding::none(),
|
||||
selection.bg_fill,
|
||||
selection.stroke,
|
||||
)
|
||||
} else if frame {
|
||||
let visuals = ui.style().interact(&response);
|
||||
let expansion = if response.hovered {
|
||||
|
@ -467,7 +472,7 @@ impl Widget for ImageButton {
|
|||
};
|
||||
(
|
||||
expansion,
|
||||
visuals.corner_radius,
|
||||
visuals.rounding,
|
||||
visuals.bg_fill,
|
||||
visuals.bg_stroke,
|
||||
)
|
||||
|
@ -477,7 +482,7 @@ impl Widget for ImageButton {
|
|||
|
||||
// Draw frame background (for transparent images):
|
||||
ui.painter()
|
||||
.rect_filled(rect.expand2(expansion), corner_radius, fill);
|
||||
.rect_filled(rect.expand2(expansion), rounding, fill);
|
||||
|
||||
let image_rect = ui
|
||||
.layout()
|
||||
|
@ -487,7 +492,7 @@ impl Widget for ImageButton {
|
|||
|
||||
// Draw frame outline:
|
||||
ui.painter()
|
||||
.rect_stroke(rect.expand2(expansion), corner_radius, stroke);
|
||||
.rect_stroke(rect.expand2(expansion), rounding, stroke);
|
||||
}
|
||||
|
||||
response
|
||||
|
|
|
@ -61,7 +61,7 @@ fn show_hsva(ui: &mut Ui, color: Hsva, desired_size: Vec2) -> Response {
|
|||
} else {
|
||||
ui.painter().add(RectShape {
|
||||
rect,
|
||||
corner_radius: 2.0,
|
||||
rounding: Rounding::same(2.0),
|
||||
fill: color.into(),
|
||||
stroke: Stroke::new(3.0, color.to_opaque()),
|
||||
});
|
||||
|
@ -90,9 +90,15 @@ fn color_button(ui: &mut Ui, color: Color32, open: bool) -> Response {
|
|||
ui.painter().rect_filled(left_half, 0.0, color);
|
||||
ui.painter().rect_filled(right_half, 0.0, color.to_opaque());
|
||||
|
||||
let corner_radius = visuals.corner_radius.at_most(2.0);
|
||||
let rounding = Rounding {
|
||||
nw: visuals.rounding.nw.at_most(2.0),
|
||||
ne: visuals.rounding.ne.at_most(2.0),
|
||||
sw: visuals.rounding.sw.at_most(2.0),
|
||||
se: visuals.rounding.se.at_most(2.0),
|
||||
};
|
||||
|
||||
ui.painter()
|
||||
.rect_stroke(rect, corner_radius, (2.0, visuals.bg_fill)); // fill is intentional, because default style has no border
|
||||
.rect_stroke(rect, rounding, (2.0, visuals.bg_fill)); // fill is intentional, because default style has no border
|
||||
}
|
||||
|
||||
response
|
||||
|
@ -308,8 +314,10 @@ fn color_picker_hsvag_2d(ui: &mut Ui, hsva: &mut HsvaGamma, alpha: Alpha) {
|
|||
color_slider_2d(ui, v, s, |v, s| HsvaGamma { s, v, ..opaque }.into());
|
||||
}
|
||||
|
||||
//// Shows a color picker where the user can change the given [`Hsva`] color.
|
||||
///
|
||||
/// Returns `true` on change.
|
||||
fn color_picker_hsva_2d(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> bool {
|
||||
pub fn color_picker_hsva_2d(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> bool {
|
||||
let mut hsvag = HsvaGamma::from(*hsva);
|
||||
ui.vertical(|ui| {
|
||||
color_picker_hsvag_2d(ui, &mut hsvag, alpha);
|
||||
|
@ -323,7 +331,7 @@ fn color_picker_hsva_2d(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// Shows a color picker where the user can change the given color.
|
||||
/// Shows a color picker where the user can change the given [`Color32`] color.
|
||||
///
|
||||
/// Returns `true` on change.
|
||||
pub fn color_picker_color32(ui: &mut Ui, srgba: &mut Color32, alpha: Alpha) -> bool {
|
||||
|
@ -335,19 +343,19 @@ pub fn color_picker_color32(ui: &mut Ui, srgba: &mut Color32, alpha: Alpha) -> b
|
|||
}
|
||||
|
||||
pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Response {
|
||||
let pupup_id = ui.auto_id_with("popup");
|
||||
let open = ui.memory().is_popup_open(pupup_id);
|
||||
let popup_id = ui.auto_id_with("popup");
|
||||
let open = ui.memory().is_popup_open(popup_id);
|
||||
let mut button_response = color_button(ui, (*hsva).into(), open);
|
||||
if ui.style().explanation_tooltips {
|
||||
button_response = button_response.on_hover_text("Click to edit color");
|
||||
}
|
||||
|
||||
if button_response.clicked() {
|
||||
ui.memory().toggle_popup(pupup_id);
|
||||
ui.memory().toggle_popup(popup_id);
|
||||
}
|
||||
// TODO: make it easier to show a temporary popup that closes when you click outside it
|
||||
if ui.memory().is_popup_open(pupup_id) {
|
||||
let area_response = Area::new(pupup_id)
|
||||
if ui.memory().is_popup_open(popup_id) {
|
||||
let area_response = Area::new(popup_id)
|
||||
.order(Order::Foreground)
|
||||
.default_pos(button_response.rect.max)
|
||||
.show(ui.ctx(), |ui| {
|
||||
|
@ -427,5 +435,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 {
|
||||
f(ctx.memory().data.get_temp_mut_or_default(Id::null()))
|
||||
f(ctx.data().get_temp_mut_or_default(Id::null()))
|
||||
}
|
||||
|
|
|
@ -159,8 +159,8 @@ impl<'a> Widget for DragValue<'a> {
|
|||
max_decimals,
|
||||
} = self;
|
||||
|
||||
let is_slow_speed =
|
||||
ui.input().modifiers.shift_only() && ui.memory().is_being_dragged(ui.next_auto_id());
|
||||
let shift = ui.input().modifiers.shift_only();
|
||||
let is_slow_speed = shift && ui.memory().is_being_dragged(ui.next_auto_id());
|
||||
|
||||
let old_value = get(&mut get_set_value);
|
||||
let value = clamp_to_range(old_value, clamp_range.clone());
|
||||
|
@ -195,7 +195,7 @@ impl<'a> Widget for DragValue<'a> {
|
|||
TextEdit::singleline(&mut value_text)
|
||||
.id(kb_edit_id)
|
||||
.desired_width(button_width)
|
||||
.text_style(TextStyle::Monospace),
|
||||
.font(TextStyle::Monospace),
|
||||
);
|
||||
if let Ok(parsed_value) = value_text.parse() {
|
||||
let parsed_value = clamp_to_range(parsed_value, clamp_range);
|
||||
|
|
|
@ -2,17 +2,31 @@ use crate::*;
|
|||
|
||||
/// An widget to show an image of a given size.
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// # let my_texture_id = egui::TextureId::User(0);
|
||||
/// ui.add(egui::Image::new(my_texture_id, [640.0, 480.0]));
|
||||
/// In order to display an image you must first acquire a [`TextureHandle`]
|
||||
/// using [`Context::load_texture`].
|
||||
///
|
||||
/// // Shorter version:
|
||||
/// ui.image(my_texture_id, [640.0, 480.0]);
|
||||
/// # });
|
||||
/// ```
|
||||
/// struct MyImage {
|
||||
/// texture: Option<egui::TextureHandle>,
|
||||
/// }
|
||||
///
|
||||
/// impl MyImage {
|
||||
/// fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
/// let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
|
||||
/// // Load the texture only once.
|
||||
/// ui.ctx().load_texture("my-image", egui::ColorImage::example())
|
||||
/// });
|
||||
///
|
||||
/// // Show the image:
|
||||
/// ui.add(egui::Image::new(texture, texture.size_vec2()));
|
||||
///
|
||||
/// // Shorter version:
|
||||
/// ui.image(texture, texture.size_vec2());
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Se also [`crate::ImageButton`].
|
||||
/// Se also [`crate::Ui::image`] and [`crate::ImageButton`].
|
||||
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Image {
|
||||
|
@ -25,9 +39,9 @@ pub struct Image {
|
|||
}
|
||||
|
||||
impl Image {
|
||||
pub fn new(texture_id: TextureId, size: impl Into<Vec2>) -> Self {
|
||||
pub fn new(texture_id: impl Into<TextureId>, size: impl Into<Vec2>) -> Self {
|
||||
Self {
|
||||
texture_id,
|
||||
texture_id: texture_id.into(),
|
||||
uv: Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)),
|
||||
size: size.into(),
|
||||
bg_fill: Default::default(),
|
||||
|
|
|
@ -2,6 +2,8 @@ use crate::{widget_text::WidgetTextGalley, *};
|
|||
|
||||
/// Static text.
|
||||
///
|
||||
/// Usually it is more convenient to use [`Ui::label`].
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// ui.label("Equivalent");
|
||||
|
@ -49,7 +51,7 @@ impl Label {
|
|||
/// By calling this you can turn the label into a button of sorts.
|
||||
/// This will also give the label the hover-effect of a button, but without the frame.
|
||||
///
|
||||
/// ``` rust
|
||||
/// ```
|
||||
/// # use egui::{Label, Sense};
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// if ui.add(Label::new("click me").sense(Sense::click())).clicked() {
|
||||
|
@ -82,7 +84,9 @@ impl Label {
|
|||
}
|
||||
|
||||
let valign = ui.layout().vertical_align();
|
||||
let mut text_job = self.text.into_text_job(ui.style(), TextStyle::Body, valign);
|
||||
let mut text_job = self
|
||||
.text
|
||||
.into_text_job(ui.style(), FontSelection::Default, valign);
|
||||
|
||||
let should_wrap = self.wrap.unwrap_or_else(|| ui.wrap_text());
|
||||
let available_width = ui.available_width();
|
||||
|
@ -106,7 +110,7 @@ impl Label {
|
|||
if let Some(first_section) = text_job.job.sections.first_mut() {
|
||||
first_section.leading_space = first_row_indentation;
|
||||
}
|
||||
let text_galley = text_job.into_galley(ui.fonts());
|
||||
let text_galley = text_job.into_galley(&*ui.fonts());
|
||||
|
||||
let pos = pos2(ui.max_rect().left(), ui.cursor().top());
|
||||
assert!(
|
||||
|
@ -139,7 +143,7 @@ impl Label {
|
|||
text_job.job.justify = ui.layout().horizontal_justify();
|
||||
};
|
||||
|
||||
let text_galley = text_job.into_galley(ui.fonts());
|
||||
let text_galley = text_job.into_galley(&*ui.fonts());
|
||||
let (rect, response) = ui.allocate_exact_size(text_galley.size(), self.sense);
|
||||
let pos = match text_galley.galley.job.halign {
|
||||
Align::LEFT => rect.left_top(),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::emath::NumExt;
|
||||
use crate::epaint::{Color32, RectShape, Shape, Stroke};
|
||||
use crate::epaint::{Color32, RectShape, Rounding, Shape, Stroke};
|
||||
|
||||
use super::{add_rulers_and_text, highlighted_color, Orientation, PlotConfig, RectElement};
|
||||
use crate::plot::{BarChart, ScreenTransform, Value};
|
||||
|
@ -129,7 +129,7 @@ impl Bar {
|
|||
let rect = transform.rect_from_values(&self.bounds_min(), &self.bounds_max());
|
||||
let rect = Shape::Rect(RectShape {
|
||||
rect,
|
||||
corner_radius: 0.0,
|
||||
rounding: Rounding::none(),
|
||||
fill,
|
||||
stroke,
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::emath::NumExt;
|
||||
use crate::epaint::{Color32, RectShape, Shape, Stroke};
|
||||
use crate::epaint::{Color32, RectShape, Rounding, Shape, Stroke};
|
||||
|
||||
use super::{add_rulers_and_text, highlighted_color, Orientation, PlotConfig, RectElement};
|
||||
use crate::plot::{BoxPlot, ScreenTransform, Value};
|
||||
|
@ -152,7 +152,7 @@ impl BoxElem {
|
|||
);
|
||||
let rect = Shape::Rect(RectShape {
|
||||
rect,
|
||||
corner_radius: 0.0,
|
||||
rounding: Rounding::none(),
|
||||
fill,
|
||||
stroke,
|
||||
});
|
||||
|
|
|
@ -9,11 +9,11 @@ use crate::*;
|
|||
|
||||
use super::{CustomLabelFuncRef, PlotBounds, ScreenTransform};
|
||||
use rect_elem::*;
|
||||
use values::*;
|
||||
use values::{ClosestElem, PlotGeometry};
|
||||
|
||||
pub use bar::Bar;
|
||||
pub use box_elem::{BoxElem, BoxSpread};
|
||||
pub use values::{LineStyle, MarkerShape, Value, Values};
|
||||
pub use values::{LineStyle, MarkerShape, Orientation, Value, Values};
|
||||
|
||||
mod bar;
|
||||
mod box_elem;
|
||||
|
@ -117,8 +117,8 @@ impl HLine {
|
|||
}
|
||||
|
||||
/// Highlight this line in the plot by scaling up the line.
|
||||
pub fn highlight(mut self) -> Self {
|
||||
self.highlight = true;
|
||||
pub fn highlight(mut self, highlight: bool) -> Self {
|
||||
self.highlight = highlight;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -227,8 +227,8 @@ impl VLine {
|
|||
}
|
||||
|
||||
/// Highlight this line in the plot by scaling up the line.
|
||||
pub fn highlight(mut self) -> Self {
|
||||
self.highlight = true;
|
||||
pub fn highlight(mut self, highlight: bool) -> Self {
|
||||
self.highlight = highlight;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -338,8 +338,8 @@ impl Line {
|
|||
}
|
||||
|
||||
/// Highlight this line in the plot by scaling up the line.
|
||||
pub fn highlight(mut self) -> Self {
|
||||
self.highlight = true;
|
||||
pub fn highlight(mut self, highlight: bool) -> Self {
|
||||
self.highlight = highlight;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -506,8 +506,8 @@ impl Polygon {
|
|||
|
||||
/// Highlight this polygon in the plot by scaling up the stroke and reducing the fill
|
||||
/// transparency.
|
||||
pub fn highlight(mut self) -> Self {
|
||||
self.highlight = true;
|
||||
pub fn highlight(mut self, highlight: bool) -> Self {
|
||||
self.highlight = highlight;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -614,8 +614,7 @@ impl PlotItem for Polygon {
|
|||
|
||||
/// Text inside the plot.
|
||||
pub struct Text {
|
||||
pub(super) text: String,
|
||||
pub(super) style: TextStyle,
|
||||
pub(super) text: WidgetText,
|
||||
pub(super) position: Value,
|
||||
pub(super) name: String,
|
||||
pub(super) highlight: bool,
|
||||
|
@ -624,11 +623,9 @@ pub struct Text {
|
|||
}
|
||||
|
||||
impl Text {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn new(position: Value, text: impl ToString) -> Self {
|
||||
pub fn new(position: Value, text: impl Into<WidgetText>) -> Self {
|
||||
Self {
|
||||
text: text.to_string(),
|
||||
style: TextStyle::Small,
|
||||
text: text.into(),
|
||||
position,
|
||||
name: Default::default(),
|
||||
highlight: false,
|
||||
|
@ -638,18 +635,12 @@ impl Text {
|
|||
}
|
||||
|
||||
/// Highlight this text in the plot by drawing a rectangle around it.
|
||||
pub fn highlight(mut self) -> Self {
|
||||
self.highlight = true;
|
||||
pub fn highlight(mut self, highlight: bool) -> Self {
|
||||
self.highlight = highlight;
|
||||
self
|
||||
}
|
||||
|
||||
/// Text style. Default is `TextStyle::Small`.
|
||||
pub fn style(mut self, style: TextStyle) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
/// Text color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
|
||||
/// Text color.
|
||||
pub fn color(mut self, color: impl Into<Color32>) -> Self {
|
||||
self.color = color.into();
|
||||
self
|
||||
|
@ -681,14 +672,23 @@ impl PlotItem for Text {
|
|||
} else {
|
||||
self.color
|
||||
};
|
||||
|
||||
let galley =
|
||||
self.text
|
||||
.clone()
|
||||
.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Small);
|
||||
|
||||
let pos = transform.position_from_value(&self.position);
|
||||
let galley = ui
|
||||
.fonts()
|
||||
.layout_no_wrap(self.text.clone(), self.style, color);
|
||||
let rect = self
|
||||
.anchor
|
||||
.anchor_rect(Rect::from_min_size(pos, galley.size()));
|
||||
shapes.push(Shape::galley(rect.min, galley));
|
||||
|
||||
let mut text_shape = epaint::TextShape::new(rect.min, galley.galley);
|
||||
if !galley.galley_has_color {
|
||||
text_shape.override_text_color = Some(color);
|
||||
}
|
||||
shapes.push(text_shape.into());
|
||||
|
||||
if self.highlight {
|
||||
shapes.push(Shape::rect_stroke(
|
||||
rect.expand(2.0),
|
||||
|
@ -763,8 +763,8 @@ impl Points {
|
|||
}
|
||||
|
||||
/// Highlight these points in the plot by scaling up their markers.
|
||||
pub fn highlight(mut self) -> Self {
|
||||
self.highlight = true;
|
||||
pub fn highlight(mut self, highlight: bool) -> Self {
|
||||
self.highlight = highlight;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -979,8 +979,8 @@ impl Arrows {
|
|||
}
|
||||
|
||||
/// Highlight these arrows in the plot.
|
||||
pub fn highlight(mut self) -> Self {
|
||||
self.highlight = true;
|
||||
pub fn highlight(mut self, highlight: bool) -> Self {
|
||||
self.highlight = highlight;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -1087,12 +1087,16 @@ pub struct PlotImage {
|
|||
|
||||
impl PlotImage {
|
||||
/// Create a new image with position and size in plot coordinates.
|
||||
pub fn new(texture_id: TextureId, position: Value, size: impl Into<Vec2>) -> Self {
|
||||
pub fn new(
|
||||
texture_id: impl Into<TextureId>,
|
||||
center_position: Value,
|
||||
size: impl Into<Vec2>,
|
||||
) -> Self {
|
||||
Self {
|
||||
position,
|
||||
position: center_position,
|
||||
name: Default::default(),
|
||||
highlight: false,
|
||||
texture_id,
|
||||
texture_id: texture_id.into(),
|
||||
uv: Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)),
|
||||
size: size.into(),
|
||||
bg_fill: Default::default(),
|
||||
|
@ -1101,8 +1105,8 @@ impl PlotImage {
|
|||
}
|
||||
|
||||
/// Highlight this image in the plot.
|
||||
pub fn highlight(mut self) -> Self {
|
||||
self.highlight = true;
|
||||
pub fn highlight(mut self, highlight: bool) -> Self {
|
||||
self.highlight = highlight;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -1291,8 +1295,8 @@ impl BarChart {
|
|||
}
|
||||
|
||||
/// Highlight all plot elements.
|
||||
pub fn highlight(mut self) -> Self {
|
||||
self.highlight = true;
|
||||
pub fn highlight(mut self, highlight: bool) -> Self {
|
||||
self.highlight = highlight;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -1454,8 +1458,8 @@ impl BoxPlot {
|
|||
}
|
||||
|
||||
/// Highlight all plot elements.
|
||||
pub fn highlight(mut self) -> Self {
|
||||
self.highlight = true;
|
||||
pub fn highlight(mut self, highlight: bool) -> Self {
|
||||
self.highlight = highlight;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -1617,13 +1621,15 @@ fn add_rulers_and_text(
|
|||
text
|
||||
});
|
||||
|
||||
let font_id = TextStyle::Body.resolve(plot.ui.style());
|
||||
|
||||
let corner_value = elem.corner_value();
|
||||
shapes.push(Shape::text(
|
||||
plot.ui.fonts(),
|
||||
&*plot.ui.fonts(),
|
||||
plot.transform.position_from_value(&corner_value) + vec2(3.0, -2.0),
|
||||
Align2::LEFT_BOTTOM,
|
||||
text,
|
||||
TextStyle::Body,
|
||||
font_id,
|
||||
plot.ui.visuals().text_color(),
|
||||
));
|
||||
}
|
||||
|
@ -1673,12 +1679,14 @@ pub(super) fn rulers_at_value(
|
|||
}
|
||||
};
|
||||
|
||||
let font_id = TextStyle::Body.resolve(plot.ui.style());
|
||||
|
||||
shapes.push(Shape::text(
|
||||
plot.ui.fonts(),
|
||||
&*plot.ui.fonts(),
|
||||
pointer + vec2(3.0, -2.0),
|
||||
Align2::LEFT_BOTTOM,
|
||||
text,
|
||||
TextStyle::Body,
|
||||
font_id,
|
||||
plot.ui.visuals().text_color(),
|
||||
));
|
||||
}
|
||||
|
|
|
@ -125,6 +125,7 @@ impl ToString for LineStyle {
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Determines whether a plot element is vertically or horizontally oriented.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Orientation {
|
||||
Horizontal,
|
||||
|
@ -139,20 +140,12 @@ impl Default for Orientation {
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Values {
|
||||
pub(super) values: Vec<Value>,
|
||||
generator: Option<ExplicitGenerator>,
|
||||
}
|
||||
|
||||
impl Default for Values {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
values: Vec::new(),
|
||||
generator: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Values {
|
||||
pub fn from_values(values: Vec<Value>) -> Self {
|
||||
Self {
|
||||
|
@ -297,7 +290,7 @@ pub enum MarkerShape {
|
|||
|
||||
impl MarkerShape {
|
||||
/// Get a vector containing all marker shapes.
|
||||
pub fn all() -> impl Iterator<Item = MarkerShape> {
|
||||
pub fn all() -> impl ExactSizeIterator<Item = MarkerShape> {
|
||||
[
|
||||
Self::Circle,
|
||||
Self::Diamond,
|
||||
|
@ -342,7 +335,7 @@ struct ExplicitGenerator {
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Result of [`PlotItem::find_closest()`] search, identifies an element inside the item for immediate use
|
||||
/// Result of [`super::PlotItem::find_closest()`] search, identifies an element inside the item for immediate use
|
||||
pub(crate) struct ClosestElem {
|
||||
/// Position of hovered-over value (or bar/box-plot/...) in PlotItem
|
||||
pub index: usize,
|
||||
|
|
|
@ -29,7 +29,7 @@ impl Corner {
|
|||
}
|
||||
|
||||
/// The configuration for a plot legend.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Legend {
|
||||
pub text_style: TextStyle,
|
||||
pub background_alpha: f32,
|
||||
|
@ -82,16 +82,18 @@ impl LegendEntry {
|
|||
}
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut Ui, text: String) -> Response {
|
||||
fn ui(&mut self, ui: &mut Ui, text: String, text_style: &TextStyle) -> Response {
|
||||
let Self {
|
||||
color,
|
||||
checked,
|
||||
hovered,
|
||||
} = self;
|
||||
|
||||
let galley =
|
||||
ui.fonts()
|
||||
.layout_delayed_color(text, ui.style().body_text_style, f32::INFINITY);
|
||||
let font_id = text_style.resolve(ui.style());
|
||||
|
||||
let galley = ui
|
||||
.fonts()
|
||||
.layout_delayed_color(text, font_id, f32::INFINITY);
|
||||
|
||||
let icon_size = galley.size().y;
|
||||
let icon_spacing = icon_size / 5.0;
|
||||
|
@ -236,10 +238,9 @@ impl Widget for &mut LegendWidget {
|
|||
let mut legend_ui = ui.child_ui(legend_rect, layout);
|
||||
legend_ui
|
||||
.scope(|ui| {
|
||||
ui.style_mut().body_text_style = config.text_style;
|
||||
let background_frame = Frame {
|
||||
margin: vec2(8.0, 4.0),
|
||||
corner_radius: ui.style().visuals.window_corner_radius,
|
||||
rounding: ui.style().visuals.window_rounding,
|
||||
shadow: epaint::Shadow::default(),
|
||||
fill: ui.style().visuals.extreme_bg_color,
|
||||
stroke: ui.style().visuals.window_stroke(),
|
||||
|
@ -249,7 +250,7 @@ impl Widget for &mut LegendWidget {
|
|||
.show(ui, |ui| {
|
||||
entries
|
||||
.iter_mut()
|
||||
.map(|(name, entry)| entry.ui(ui, name.clone()))
|
||||
.map(|(name, entry)| entry.ui(ui, name.clone(), &config.text_style))
|
||||
.reduce(|r1, r2| r1.union(r2))
|
||||
.unwrap()
|
||||
})
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Simple plotting library.
|
||||
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::*;
|
||||
use epaint::ahash::AHashSet;
|
||||
use epaint::color::Hsva;
|
||||
|
@ -10,7 +12,7 @@ use transform::{PlotBounds, ScreenTransform};
|
|||
|
||||
pub use items::{
|
||||
Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape,
|
||||
PlotImage, Points, Polygon, Text, VLine, Value, Values,
|
||||
Orientation, PlotImage, Points, Polygon, Text, VLine, Value, Values,
|
||||
};
|
||||
pub use legend::{Corner, Legend};
|
||||
|
||||
|
@ -21,6 +23,9 @@ mod transform;
|
|||
type CustomLabelFunc = dyn Fn(&str, &Value) -> String;
|
||||
type CustomLabelFuncRef = Option<Box<CustomLabelFunc>>;
|
||||
|
||||
type AxisFormatterFn = dyn Fn(f64) -> String;
|
||||
type AxisFormatter = Option<Box<AxisFormatterFn>>;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Information about the plot that has to persist between frames.
|
||||
|
@ -32,15 +37,74 @@ struct PlotMemory {
|
|||
hidden_items: AHashSet<String>,
|
||||
min_auto_bounds: PlotBounds,
|
||||
last_screen_transform: ScreenTransform,
|
||||
/// Allows to remember the first click position when performing a boxed zoom
|
||||
last_click_pos_for_zoom: Option<Pos2>,
|
||||
}
|
||||
|
||||
impl PlotMemory {
|
||||
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
|
||||
ctx.memory().data.get_persisted(id)
|
||||
ctx.data().get_persisted(id)
|
||||
}
|
||||
|
||||
pub fn store(self, ctx: &Context, id: Id) {
|
||||
ctx.memory().data.insert_persisted(id, self);
|
||||
ctx.data().insert_persisted(id, self);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Defines how multiple plots share the same range for one or both of their axes. Can be added while building
|
||||
/// a plot with [`Plot::link_axis`]. Contains an internal state, meaning that this object should be stored by
|
||||
/// the user between frames.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct LinkedAxisGroup {
|
||||
pub(crate) link_x: bool,
|
||||
pub(crate) link_y: bool,
|
||||
pub(crate) bounds: Rc<RefCell<Option<PlotBounds>>>,
|
||||
}
|
||||
|
||||
impl LinkedAxisGroup {
|
||||
pub fn new(link_x: bool, link_y: bool) -> Self {
|
||||
Self {
|
||||
link_x,
|
||||
link_y,
|
||||
bounds: Rc::new(RefCell::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Only link the x-axis.
|
||||
pub fn x() -> Self {
|
||||
Self::new(true, false)
|
||||
}
|
||||
|
||||
/// Only link the y-axis.
|
||||
pub fn y() -> Self {
|
||||
Self::new(false, true)
|
||||
}
|
||||
|
||||
/// Link both axes. Note that this still respects the aspect ratio of the individual plots.
|
||||
pub fn both() -> Self {
|
||||
Self::new(true, true)
|
||||
}
|
||||
|
||||
/// Change whether the x-axis is linked for this group. Using this after plots in this group have been
|
||||
/// drawn in this frame already may lead to unexpected results.
|
||||
pub fn set_link_x(&mut self, link: bool) {
|
||||
self.link_x = link;
|
||||
}
|
||||
|
||||
/// Change whether the y-axis is linked for this group. Using this after plots in this group have been
|
||||
/// drawn in this frame already may lead to unexpected results.
|
||||
pub fn set_link_y(&mut self, link: bool) {
|
||||
self.link_y = link;
|
||||
}
|
||||
|
||||
fn get(&self) -> Option<PlotBounds> {
|
||||
*self.bounds.borrow()
|
||||
}
|
||||
|
||||
fn set(&self, bounds: PlotBounds) {
|
||||
*self.bounds.borrow_mut() = Some(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,6 +134,9 @@ pub struct Plot {
|
|||
allow_drag: bool,
|
||||
min_auto_bounds: PlotBounds,
|
||||
margin_fraction: Vec2,
|
||||
allow_boxed_zoom: bool,
|
||||
boxed_zoom_pointer_button: PointerButton,
|
||||
linked_axes: Option<LinkedAxisGroup>,
|
||||
|
||||
min_size: Vec2,
|
||||
width: Option<f32>,
|
||||
|
@ -80,6 +147,7 @@ pub struct Plot {
|
|||
show_x: bool,
|
||||
show_y: bool,
|
||||
custom_label_func: CustomLabelFuncRef,
|
||||
axis_formatters: [AxisFormatter; 2],
|
||||
legend_config: Option<Legend>,
|
||||
show_background: bool,
|
||||
show_axes: [bool; 2],
|
||||
|
@ -97,6 +165,9 @@ impl Plot {
|
|||
allow_drag: true,
|
||||
min_auto_bounds: PlotBounds::NOTHING,
|
||||
margin_fraction: Vec2::splat(0.05),
|
||||
allow_boxed_zoom: true,
|
||||
boxed_zoom_pointer_button: PointerButton::Secondary,
|
||||
linked_axes: None,
|
||||
|
||||
min_size: Vec2::splat(64.0),
|
||||
width: None,
|
||||
|
@ -107,6 +178,7 @@ impl Plot {
|
|||
show_x: true,
|
||||
show_y: true,
|
||||
custom_label_func: None,
|
||||
axis_formatters: [None, None], // [None; 2] requires Copy
|
||||
legend_config: None,
|
||||
show_background: true,
|
||||
show_axes: [true; 2],
|
||||
|
@ -181,6 +253,20 @@ impl Plot {
|
|||
self
|
||||
}
|
||||
|
||||
/// Whether to allow zooming in the plot by dragging out a box with the secondary mouse button.
|
||||
///
|
||||
/// Default: `true`.
|
||||
pub fn allow_boxed_zoom(mut self, on: bool) -> Self {
|
||||
self.allow_boxed_zoom = on;
|
||||
self
|
||||
}
|
||||
|
||||
/// Config the button pointer to use for boxed zooming. Default: `Secondary`
|
||||
pub fn boxed_zoom_pointer_button(mut self, boxed_zoom_pointer_button: PointerButton) -> Self {
|
||||
self.boxed_zoom_pointer_button = boxed_zoom_pointer_button;
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether to allow dragging in the plot to move the bounds. Default: `true`.
|
||||
pub fn allow_drag(mut self, on: bool) -> Self {
|
||||
self.allow_drag = on;
|
||||
|
@ -208,11 +294,35 @@ impl Plot {
|
|||
/// .show(ui, |plot_ui| plot_ui.line(line));
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn custom_label_func<F: 'static + Fn(&str, &Value) -> String>(
|
||||
pub fn custom_label_func(
|
||||
mut self,
|
||||
custom_lebel_func: F,
|
||||
custom_label_func: impl Fn(&str, &Value) -> String + 'static,
|
||||
) -> Self {
|
||||
self.custom_label_func = Some(Box::new(custom_lebel_func));
|
||||
self.custom_label_func = Some(Box::new(custom_label_func));
|
||||
self
|
||||
}
|
||||
|
||||
/// Provide a function to customize the labels for the X axis.
|
||||
///
|
||||
/// This is useful for custom input domains, e.g. date/time.
|
||||
///
|
||||
/// If axis labels should not appear for certain values or beyond a certain zoom/resolution,
|
||||
/// the formatter function can return empty strings. This is also useful if your domain is
|
||||
/// discrete (e.g. only full days in a calendar).
|
||||
pub fn x_axis_formatter(mut self, func: impl Fn(f64) -> String + 'static) -> Self {
|
||||
self.axis_formatters[0] = Some(Box::new(func));
|
||||
self
|
||||
}
|
||||
|
||||
/// Provide a function to customize the labels for the Y axis.
|
||||
///
|
||||
/// This is useful for custom value representation, e.g. percentage or units.
|
||||
///
|
||||
/// If axis labels should not appear for certain values or beyond a certain zoom/resolution,
|
||||
/// the formatter function can return empty strings. This is also useful if your Y values are
|
||||
/// discrete (e.g. only integers).
|
||||
pub fn y_axis_formatter(mut self, func: impl Fn(f64) -> String + 'static) -> Self {
|
||||
self.axis_formatters[1] = Some(Box::new(func));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -252,6 +362,13 @@ impl Plot {
|
|||
self
|
||||
}
|
||||
|
||||
/// Add a [`LinkedAxisGroup`] so that this plot will share the bounds with other plots that have this
|
||||
/// group assigned. A plot cannot belong to more than one group.
|
||||
pub fn link_axis(mut self, group: LinkedAxisGroup) -> Self {
|
||||
self.linked_axes = Some(group);
|
||||
self
|
||||
}
|
||||
|
||||
/// Interact with and add items to the plot and finally draw it.
|
||||
pub fn show<R>(self, ui: &mut Ui, build_fn: impl FnOnce(&mut PlotUi) -> R) -> InnerResponse<R> {
|
||||
let Self {
|
||||
|
@ -260,6 +377,8 @@ impl Plot {
|
|||
center_y_axis,
|
||||
allow_zoom,
|
||||
allow_drag,
|
||||
allow_boxed_zoom,
|
||||
boxed_zoom_pointer_button: boxed_zoom_pointer,
|
||||
min_auto_bounds,
|
||||
margin_fraction,
|
||||
width,
|
||||
|
@ -270,9 +389,11 @@ impl Plot {
|
|||
mut show_x,
|
||||
mut show_y,
|
||||
custom_label_func,
|
||||
axis_formatters,
|
||||
legend_config,
|
||||
show_background,
|
||||
show_axes,
|
||||
linked_axes,
|
||||
} = self;
|
||||
|
||||
// Determine the size of the plot in the UI
|
||||
|
@ -315,6 +436,7 @@ impl Plot {
|
|||
center_x_axis,
|
||||
center_y_axis,
|
||||
),
|
||||
last_click_pos_for_zoom: None,
|
||||
});
|
||||
|
||||
// If the min bounds changed, recalculate everything.
|
||||
|
@ -333,6 +455,7 @@ impl Plot {
|
|||
mut hovered_entry,
|
||||
mut hidden_items,
|
||||
last_screen_transform,
|
||||
mut last_click_pos_for_zoom,
|
||||
..
|
||||
} = memory;
|
||||
|
||||
|
@ -356,7 +479,7 @@ impl Plot {
|
|||
if show_background {
|
||||
ui.painter().sub_region(rect).add(epaint::RectShape {
|
||||
rect,
|
||||
corner_radius: 2.0,
|
||||
rounding: Rounding::same(2.0),
|
||||
fill: ui.visuals().extreme_bg_color,
|
||||
stroke: ui.visuals().widgets.noninteractive.bg_stroke,
|
||||
});
|
||||
|
@ -385,6 +508,22 @@ impl Plot {
|
|||
// --- Bound computation ---
|
||||
let mut bounds = *last_screen_transform.bounds();
|
||||
|
||||
// Transfer the bounds from a link group.
|
||||
if let Some(axes) = linked_axes.as_ref() {
|
||||
if let Some(linked_bounds) = axes.get() {
|
||||
if axes.link_x {
|
||||
bounds.min[0] = linked_bounds.min[0];
|
||||
bounds.max[0] = linked_bounds.max[0];
|
||||
}
|
||||
if axes.link_y {
|
||||
bounds.min[1] = linked_bounds.min[1];
|
||||
bounds.max[1] = linked_bounds.max[1];
|
||||
}
|
||||
// Turn off auto bounds to keep it from overriding what we just set.
|
||||
auto_bounds = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow double clicking to reset to automatic bounds.
|
||||
auto_bounds |= response.double_clicked_by(PointerButton::Primary);
|
||||
|
||||
|
@ -401,7 +540,10 @@ impl Plot {
|
|||
|
||||
// Enforce equal aspect ratio.
|
||||
if let Some(data_aspect) = data_aspect {
|
||||
transform.set_aspect(data_aspect as f64);
|
||||
let preserve_y = linked_axes
|
||||
.as_ref()
|
||||
.map_or(false, |group| group.link_y && !group.link_x);
|
||||
transform.set_aspect(data_aspect as f64, preserve_y);
|
||||
}
|
||||
|
||||
// Dragging
|
||||
|
@ -412,6 +554,53 @@ impl Plot {
|
|||
}
|
||||
|
||||
// Zooming
|
||||
let mut boxed_zoom_rect = None;
|
||||
if allow_boxed_zoom {
|
||||
// Save last click to allow boxed zooming
|
||||
if response.drag_started() && response.dragged_by(boxed_zoom_pointer) {
|
||||
// it would be best for egui that input has a memory of the last click pos because it's a common pattern
|
||||
last_click_pos_for_zoom = response.hover_pos();
|
||||
}
|
||||
let box_start_pos = last_click_pos_for_zoom;
|
||||
let box_end_pos = response.hover_pos();
|
||||
if let (Some(box_start_pos), Some(box_end_pos)) = (box_start_pos, box_end_pos) {
|
||||
// while dragging prepare a Shape and draw it later on top of the plot
|
||||
if response.dragged_by(boxed_zoom_pointer) {
|
||||
response = response.on_hover_cursor(CursorIcon::ZoomIn);
|
||||
let rect = epaint::Rect::from_two_pos(box_start_pos, box_end_pos);
|
||||
boxed_zoom_rect = Some((
|
||||
epaint::RectShape::stroke(
|
||||
rect,
|
||||
0.0,
|
||||
epaint::Stroke::new(4., Color32::DARK_BLUE),
|
||||
), // Outer stroke
|
||||
epaint::RectShape::stroke(
|
||||
rect,
|
||||
0.0,
|
||||
epaint::Stroke::new(2., Color32::WHITE),
|
||||
), // Inner stroke
|
||||
));
|
||||
}
|
||||
// when the click is release perform the zoom
|
||||
if response.drag_released() {
|
||||
let box_start_pos = transform.value_from_position(box_start_pos);
|
||||
let box_end_pos = transform.value_from_position(box_end_pos);
|
||||
let new_bounds = PlotBounds {
|
||||
min: [box_start_pos.x, box_end_pos.y],
|
||||
max: [box_end_pos.x, box_start_pos.y],
|
||||
};
|
||||
if new_bounds.is_valid() {
|
||||
*transform.bounds_mut() = new_bounds;
|
||||
auto_bounds = false;
|
||||
} else {
|
||||
auto_bounds = true;
|
||||
}
|
||||
// reset the boxed zoom state
|
||||
last_click_pos_for_zoom = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if allow_zoom {
|
||||
if let Some(hover_pos) = response.hover_pos() {
|
||||
let zoom_factor = if data_aspect.is_some() {
|
||||
|
@ -442,23 +631,34 @@ impl Plot {
|
|||
show_x,
|
||||
show_y,
|
||||
custom_label_func,
|
||||
axis_formatters,
|
||||
show_axes,
|
||||
transform: transform.clone(),
|
||||
};
|
||||
prepared.ui(ui, &response);
|
||||
|
||||
if let Some(boxed_zoom_rect) = boxed_zoom_rect {
|
||||
ui.painter().sub_region(rect).add(boxed_zoom_rect.0);
|
||||
ui.painter().sub_region(rect).add(boxed_zoom_rect.1);
|
||||
}
|
||||
|
||||
if let Some(mut legend) = legend {
|
||||
ui.add(&mut legend);
|
||||
hidden_items = legend.get_hidden_items();
|
||||
hovered_entry = legend.get_hovered_entry_name();
|
||||
}
|
||||
|
||||
if let Some(group) = linked_axes.as_ref() {
|
||||
group.set(*transform.bounds());
|
||||
}
|
||||
|
||||
let memory = PlotMemory {
|
||||
auto_bounds,
|
||||
hovered_entry,
|
||||
hidden_items,
|
||||
min_auto_bounds,
|
||||
last_screen_transform: transform,
|
||||
last_click_pos_for_zoom,
|
||||
};
|
||||
memory.store(ui.ctx(), plot_id);
|
||||
|
||||
|
@ -473,13 +673,13 @@ impl Plot {
|
|||
}
|
||||
|
||||
/// Provides methods to interact with a plot while building it. It is the single argument of the closure
|
||||
/// provided to `Plot::show`. See [`Plot`] for an example of how to use it.
|
||||
/// provided to [`Plot::show`]. See [`Plot`] for an example of how to use it.
|
||||
pub struct PlotUi {
|
||||
items: Vec<Box<dyn PlotItem>>,
|
||||
next_auto_color_idx: usize,
|
||||
last_screen_transform: ScreenTransform,
|
||||
response: Response,
|
||||
ctx: CtxRef,
|
||||
ctx: Context,
|
||||
}
|
||||
|
||||
impl PlotUi {
|
||||
|
@ -491,7 +691,7 @@ impl PlotUi {
|
|||
Hsva::new(h, 0.85, 0.5, 1.0).into() // TODO: OkLab or some other perspective color space
|
||||
}
|
||||
|
||||
pub fn ctx(&self) -> &CtxRef {
|
||||
pub fn ctx(&self) -> &Context {
|
||||
&self.ctx
|
||||
}
|
||||
|
||||
|
@ -650,6 +850,7 @@ struct PreparedPlot {
|
|||
show_x: bool,
|
||||
show_y: bool,
|
||||
custom_label_func: CustomLabelFuncRef,
|
||||
axis_formatters: [AxisFormatter; 2],
|
||||
show_axes: [bool; 2],
|
||||
transform: ScreenTransform,
|
||||
}
|
||||
|
@ -680,10 +881,15 @@ impl PreparedPlot {
|
|||
}
|
||||
|
||||
fn paint_axis(&self, ui: &Ui, axis: usize, shapes: &mut Vec<Shape>) {
|
||||
let Self { transform, .. } = self;
|
||||
let Self {
|
||||
transform,
|
||||
axis_formatters,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let bounds = transform.bounds();
|
||||
let text_style = TextStyle::Body;
|
||||
|
||||
let font_id = TextStyle::Body.resolve(ui.style());
|
||||
|
||||
let base: i64 = 10;
|
||||
let basef = base as f64;
|
||||
|
@ -739,18 +945,26 @@ impl PreparedPlot {
|
|||
|
||||
if text_alpha > 0.0 {
|
||||
let color = color_from_alpha(ui, text_alpha);
|
||||
let text = emath::round_to_decimals(value_main, 5).to_string(); // hack
|
||||
|
||||
let galley = ui.painter().layout_no_wrap(text, text_style, color);
|
||||
let text: String = if let Some(formatter) = axis_formatters[axis].as_deref() {
|
||||
formatter(value_main)
|
||||
} else {
|
||||
emath::round_to_decimals(value_main, 5).to_string() // hack
|
||||
};
|
||||
|
||||
let mut text_pos = pos_in_gui + vec2(1.0, -galley.size().y);
|
||||
// Custom formatters can return empty string to signal "no label at this resolution"
|
||||
if !text.is_empty() {
|
||||
let galley = ui.painter().layout_no_wrap(text, font_id.clone(), color);
|
||||
|
||||
// Make sure we see the labels, even if the axis is off-screen:
|
||||
text_pos[1 - axis] = text_pos[1 - axis]
|
||||
.at_most(transform.frame().max[1 - axis] - galley.size()[1 - axis] - 2.0)
|
||||
.at_least(transform.frame().min[1 - axis] + 1.0);
|
||||
let mut text_pos = pos_in_gui + vec2(1.0, -galley.size().y);
|
||||
|
||||
shapes.push(Shape::galley(text_pos, galley));
|
||||
// Make sure we see the labels, even if the axis is off-screen:
|
||||
text_pos[1 - axis] = text_pos[1 - axis]
|
||||
.at_most(transform.frame().max[1 - axis] - galley.size()[1 - axis] - 2.0)
|
||||
.at_least(transform.frame().min[1 - axis] + 1.0);
|
||||
|
||||
shapes.push(Shape::galley(text_pos, galley));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -178,6 +178,10 @@ impl ScreenTransform {
|
|||
&self.bounds
|
||||
}
|
||||
|
||||
pub fn bounds_mut(&mut self) -> &mut PlotBounds {
|
||||
&mut self.bounds
|
||||
}
|
||||
|
||||
pub fn translate_bounds(&mut self, mut delta_pos: Vec2) {
|
||||
if self.x_centered {
|
||||
delta_pos.x = 0.;
|
||||
|
@ -273,13 +277,20 @@ impl ScreenTransform {
|
|||
(self.bounds.width() / rw) / (self.bounds.height() / rh)
|
||||
}
|
||||
|
||||
pub fn set_aspect(&mut self, aspect: f64) {
|
||||
let epsilon = 1e-5;
|
||||
/// Sets the aspect ratio by either expanding the x-axis or contracting the y-axis.
|
||||
pub fn set_aspect(&mut self, aspect: f64, preserve_y: bool) {
|
||||
let current_aspect = self.get_aspect();
|
||||
if current_aspect < aspect - epsilon {
|
||||
|
||||
let epsilon = 1e-5;
|
||||
if (current_aspect - aspect).abs() < epsilon {
|
||||
// Don't make any changes when the aspect is already almost correct.
|
||||
return;
|
||||
}
|
||||
|
||||
if preserve_y {
|
||||
self.bounds
|
||||
.expand_x((aspect / current_aspect - 1.0) * self.bounds.width() * 0.5);
|
||||
} else if current_aspect > aspect + epsilon {
|
||||
} else {
|
||||
self.bounds
|
||||
.expand_y((current_aspect / aspect - 1.0) * self.bounds.height() * 0.5);
|
||||
}
|
||||
|
|
|
@ -77,10 +77,10 @@ impl Widget for ProgressBar {
|
|||
}
|
||||
|
||||
let visuals = ui.style().visuals.clone();
|
||||
let corner_radius = outer_rect.height() / 2.0;
|
||||
let rounding = outer_rect.height() / 2.0;
|
||||
ui.painter().rect(
|
||||
outer_rect,
|
||||
corner_radius,
|
||||
rounding,
|
||||
visuals.extreme_bg_color,
|
||||
Stroke::none(),
|
||||
);
|
||||
|
@ -101,7 +101,7 @@ impl Widget for ProgressBar {
|
|||
|
||||
ui.painter().rect(
|
||||
inner_rect,
|
||||
corner_radius,
|
||||
rounding,
|
||||
Color32::from(Rgba::from(visuals.selection.bg_fill) * color_factor as f32),
|
||||
Stroke::none(),
|
||||
);
|
||||
|
@ -110,14 +110,14 @@ impl Widget for ProgressBar {
|
|||
let n_points = 20;
|
||||
let start_angle = ui.input().time as f64 * 360f64.to_radians();
|
||||
let end_angle = start_angle + 240f64.to_radians() * ui.input().time.sin();
|
||||
let circle_radius = corner_radius - 2.0;
|
||||
let circle_radius = rounding - 2.0;
|
||||
let points: Vec<Pos2> = (0..n_points)
|
||||
.map(|i| {
|
||||
let angle = lerp(start_angle..=end_angle, i as f64 / n_points as f64);
|
||||
let (sin, cos) = angle.sin_cos();
|
||||
inner_rect.right_center()
|
||||
+ circle_radius * vec2(cos as f32, sin as f32)
|
||||
+ vec2(-corner_radius, 0.0)
|
||||
+ vec2(-rounding, 0.0)
|
||||
})
|
||||
.collect();
|
||||
ui.painter().add(Shape::line(
|
||||
|
|
|
@ -64,9 +64,8 @@ impl Widget for SelectableLabel {
|
|||
if selected || response.hovered() || response.has_focus() {
|
||||
let rect = rect.expand(visuals.expansion);
|
||||
|
||||
let corner_radius = 2.0;
|
||||
ui.painter()
|
||||
.rect(rect, corner_radius, visuals.bg_fill, visuals.bg_stroke);
|
||||
.rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke);
|
||||
}
|
||||
|
||||
text.paint_with_visuals(ui.painter(), text_pos, &visuals);
|
||||
|
|
|
@ -355,7 +355,7 @@ impl<'a> Slider<'a> {
|
|||
let visuals = ui.style().interact(response);
|
||||
ui.painter().add(epaint::RectShape {
|
||||
rect: rail_rect,
|
||||
corner_radius: ui.visuals().widgets.inactive.corner_radius,
|
||||
rounding: ui.visuals().widgets.inactive.rounding,
|
||||
fill: ui.visuals().widgets.inactive.bg_fill,
|
||||
// fill: visuals.bg_fill,
|
||||
// fill: ui.visuals().extreme_bg_color,
|
||||
|
@ -466,10 +466,8 @@ impl<'a> Slider<'a> {
|
|||
}
|
||||
|
||||
fn add_contents(&mut self, ui: &mut Ui) -> Response {
|
||||
let text_style = TextStyle::Button;
|
||||
let perpendicular = ui
|
||||
.fonts()
|
||||
.row_height(text_style)
|
||||
.text_style_height(&TextStyle::Body)
|
||||
.at_least(ui.spacing().interact_size.y);
|
||||
let slider_response = self.allocate_slider_space(ui, perpendicular);
|
||||
self.slider_ui(ui, &slider_response);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::sync::Arc;
|
||||
use epaint::mutex::Arc;
|
||||
|
||||
use epaint::text::{cursor::*, Galley, LayoutJob};
|
||||
|
||||
|
@ -8,7 +8,7 @@ use super::{CCursorRange, CursorRange, TextEditOutput, TextEditState};
|
|||
|
||||
/// A text region that the user can edit the contents of.
|
||||
///
|
||||
/// See also [`Ui::text_edit_singleline`] and [`Ui::text_edit_multiline`].
|
||||
/// See also [`Ui::text_edit_singleline`] and [`Ui::text_edit_multiline`].
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
|
@ -52,11 +52,12 @@ pub struct TextEdit<'t> {
|
|||
hint_text: WidgetText,
|
||||
id: Option<Id>,
|
||||
id_source: Option<Id>,
|
||||
text_style: Option<TextStyle>,
|
||||
font_selection: FontSelection,
|
||||
text_color: Option<Color32>,
|
||||
layouter: Option<&'t mut dyn FnMut(&Ui, &str, f32) -> Arc<Galley>>,
|
||||
password: bool,
|
||||
frame: bool,
|
||||
margin: Vec2,
|
||||
multiline: bool,
|
||||
interactive: bool,
|
||||
desired_width: Option<f32>,
|
||||
|
@ -96,11 +97,12 @@ impl<'t> TextEdit<'t> {
|
|||
hint_text: Default::default(),
|
||||
id: None,
|
||||
id_source: None,
|
||||
text_style: None,
|
||||
font_selection: Default::default(),
|
||||
text_color: None,
|
||||
layouter: None,
|
||||
password: false,
|
||||
frame: true,
|
||||
margin: vec2(4.0, 2.0),
|
||||
multiline: true,
|
||||
interactive: true,
|
||||
desired_width: None,
|
||||
|
@ -115,7 +117,7 @@ impl<'t> TextEdit<'t> {
|
|||
/// - monospaced font
|
||||
/// - focus lock
|
||||
pub fn code_editor(self) -> Self {
|
||||
self.text_style(TextStyle::Monospace).lock_focus(true)
|
||||
self.font(TextStyle::Monospace).lock_focus(true)
|
||||
}
|
||||
|
||||
/// Use if you want to set an explicit `Id` for this widget.
|
||||
|
@ -142,11 +144,17 @@ impl<'t> TextEdit<'t> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||
self.text_style = Some(text_style);
|
||||
/// Pick a [`FontId`] or [`TextStyle`].
|
||||
pub fn font(mut self, font_selection: impl Into<FontSelection>) -> Self {
|
||||
self.font_selection = font_selection.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Use .font(…) instead"]
|
||||
pub fn text_style(self, text_style: TextStyle) -> Self {
|
||||
self.font(text_style)
|
||||
}
|
||||
|
||||
pub fn text_color(mut self, text_color: Color32) -> Self {
|
||||
self.text_color = Some(text_color);
|
||||
self
|
||||
|
@ -200,6 +208,12 @@ impl<'t> TextEdit<'t> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Set margin of text. Default is [4.0,2.0]
|
||||
pub fn margin(mut self, margin: Vec2) -> Self {
|
||||
self.margin = margin;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set to 0.0 to keep as small as possible.
|
||||
/// Set to [`f32::INFINITY`] to take up all available space (i.e. disable automatic word wrap).
|
||||
pub fn desired_width(mut self, desired_width: f32) -> Self {
|
||||
|
@ -264,7 +278,7 @@ impl<'t> TextEdit<'t> {
|
|||
let interactive = self.interactive;
|
||||
let where_to_put_background = ui.painter().add(Shape::Noop);
|
||||
|
||||
let margin = Vec2::new(4.0, 2.0);
|
||||
let margin = self.margin;
|
||||
let max_rect = ui.available_rect_before_wrap().shrink2(margin);
|
||||
let mut content_ui = ui.child_ui(max_rect, *ui.layout());
|
||||
let mut output = self.show_content(&mut content_ui);
|
||||
|
@ -285,7 +299,7 @@ impl<'t> TextEdit<'t> {
|
|||
if output.response.has_focus() {
|
||||
epaint::RectShape {
|
||||
rect: frame_rect,
|
||||
corner_radius: visuals.corner_radius,
|
||||
rounding: visuals.rounding,
|
||||
// fill: ui.visuals().selection.bg_fill,
|
||||
fill: ui.visuals().extreme_bg_color,
|
||||
stroke: ui.visuals().selection.stroke,
|
||||
|
@ -293,7 +307,7 @@ impl<'t> TextEdit<'t> {
|
|||
} else {
|
||||
epaint::RectShape {
|
||||
rect: frame_rect,
|
||||
corner_radius: visuals.corner_radius,
|
||||
rounding: visuals.rounding,
|
||||
fill: ui.visuals().extreme_bg_color,
|
||||
stroke: visuals.bg_stroke, // TODO: we want to show something here, or a text-edit field doesn't "pop".
|
||||
}
|
||||
|
@ -302,7 +316,7 @@ impl<'t> TextEdit<'t> {
|
|||
let visuals = &ui.style().visuals.widgets.inactive;
|
||||
epaint::RectShape {
|
||||
rect: frame_rect,
|
||||
corner_radius: visuals.corner_radius,
|
||||
rounding: visuals.rounding,
|
||||
// fill: ui.visuals().extreme_bg_color,
|
||||
// fill: visuals.bg_fill,
|
||||
fill: Color32::TRANSPARENT,
|
||||
|
@ -322,11 +336,12 @@ impl<'t> TextEdit<'t> {
|
|||
hint_text,
|
||||
id,
|
||||
id_source,
|
||||
text_style,
|
||||
font_selection,
|
||||
text_color,
|
||||
layouter,
|
||||
password,
|
||||
frame: _,
|
||||
margin: _,
|
||||
multiline,
|
||||
interactive,
|
||||
desired_width,
|
||||
|
@ -341,10 +356,9 @@ impl<'t> TextEdit<'t> {
|
|||
.unwrap_or_else(|| ui.visuals().widgets.inactive.text_color());
|
||||
|
||||
let prev_text = text.as_ref().to_owned();
|
||||
let text_style = text_style
|
||||
.or(ui.style().override_text_style)
|
||||
.unwrap_or_else(|| ui.style().body_text_style);
|
||||
let row_height = ui.fonts().row_height(text_style);
|
||||
|
||||
let font_id = font_selection.resolve(ui.style());
|
||||
let row_height = ui.fonts().row_height(&font_id);
|
||||
const MIN_WIDTH: f32 = 24.0; // Never make a `TextEdit` more narrow than this.
|
||||
let available_width = ui.available_width().at_least(MIN_WIDTH);
|
||||
let desired_width = desired_width.unwrap_or_else(|| ui.spacing().text_edit_width);
|
||||
|
@ -354,12 +368,13 @@ impl<'t> TextEdit<'t> {
|
|||
desired_width.min(available_width)
|
||||
};
|
||||
|
||||
let font_id_clone = font_id.clone();
|
||||
let mut default_layouter = move |ui: &Ui, text: &str, wrap_width: f32| {
|
||||
let text = mask_if_password(password, text);
|
||||
ui.fonts().layout_job(if multiline {
|
||||
LayoutJob::simple(text, text_style, text_color, wrap_width)
|
||||
LayoutJob::simple(text, font_id_clone.clone(), text_color, wrap_width)
|
||||
} else {
|
||||
LayoutJob::simple_singleline(text, text_style, text_color)
|
||||
LayoutJob::simple_singleline(text, font_id_clone.clone(), text_color)
|
||||
})
|
||||
};
|
||||
|
||||
|
@ -390,7 +405,8 @@ impl<'t> TextEdit<'t> {
|
|||
// dragging select text, or scroll the enclosing `ScrollArea` (if any)?
|
||||
// Since currently copying selected text in not supported on `egui_web`,
|
||||
// we prioritize touch-scrolling:
|
||||
let allow_drag_to_select = !ui.input().any_touches() || ui.memory().has_focus(id);
|
||||
let any_touches = ui.input().any_touches(); // separate line to avoid double-locking the same mutex
|
||||
let allow_drag_to_select = !any_touches || ui.memory().has_focus(id);
|
||||
|
||||
let sense = if interactive {
|
||||
if allow_drag_to_select {
|
||||
|
@ -406,7 +422,7 @@ impl<'t> TextEdit<'t> {
|
|||
let painter = ui.painter_at(text_clip_rect);
|
||||
|
||||
if interactive {
|
||||
if let Some(pointer_pos) = ui.input().pointer.interact_pos() {
|
||||
if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
|
||||
if response.hovered() && text.is_mutable() {
|
||||
ui.output().mutable_text_under_cursor = true;
|
||||
}
|
||||
|
@ -533,9 +549,9 @@ impl<'t> TextEdit<'t> {
|
|||
if text.as_ref().is_empty() && !hint_text.is_empty() {
|
||||
let hint_text_color = ui.visuals().weak_text_color();
|
||||
let galley = if multiline {
|
||||
hint_text.into_galley(ui, Some(true), desired_size.x, text_style)
|
||||
hint_text.into_galley(ui, Some(true), desired_size.x, font_id)
|
||||
} else {
|
||||
hint_text.into_galley(ui, Some(false), f32::INFINITY, text_style)
|
||||
hint_text.into_galley(ui, Some(false), f32::INFINITY, font_id)
|
||||
};
|
||||
galley.paint_with_fallback_color(&painter, response.rect.min, hint_text_color);
|
||||
}
|
||||
|
@ -665,7 +681,8 @@ fn events(
|
|||
|
||||
let mut any_change = false;
|
||||
|
||||
for event in &ui.input().events {
|
||||
let events = ui.input().events.clone(); // avoid dead-lock by cloning. TODO: optimize
|
||||
for event in &events {
|
||||
let did_mutate_text = match event {
|
||||
Event::Copy => {
|
||||
if cursor_range.is_empty() {
|
||||
|
@ -684,6 +701,15 @@ fn events(
|
|||
Some(CCursorRange::one(delete_selected(text, &cursor_range)))
|
||||
}
|
||||
}
|
||||
Event::Paste(text_to_insert) => {
|
||||
if !text_to_insert.is_empty() {
|
||||
let mut ccursor = delete_selected(text, &cursor_range);
|
||||
insert_text(&mut ccursor, text, text_to_insert);
|
||||
Some(CCursorRange::one(ccursor))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Event::Text(text_to_insert) => {
|
||||
// Newlines are handled by `Key::Enter`.
|
||||
if !text_to_insert.is_empty() && text_to_insert != "\n" && text_to_insert != "\r" {
|
||||
|
|
|
@ -34,11 +34,11 @@ pub struct TextEditState {
|
|||
|
||||
impl TextEditState {
|
||||
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
|
||||
ctx.memory().data.get_persisted(id)
|
||||
ctx.data().get_persisted(id)
|
||||
}
|
||||
|
||||
pub fn store(self, ctx: &Context, id: Id) {
|
||||
ctx.memory().data.insert_persisted(id, self);
|
||||
ctx.data().insert_persisted(id, self);
|
||||
}
|
||||
|
||||
/// The the currently selected range of characters.
|
||||
|
|
|
@ -7,9 +7,21 @@ edition = "2021"
|
|||
rust-version = "1.56"
|
||||
publish = false
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
|
||||
[features]
|
||||
default = ["persistence"]
|
||||
http = ["egui_demo_lib/http"]
|
||||
persistence = ["eframe/persistence", "egui_demo_lib/persistence"]
|
||||
screen_reader = ["eframe/screen_reader"] # experimental
|
||||
syntax_highlighting = ["egui_demo_lib/syntax_highlighting"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
eframe = { version = "0.16.0", path = "../eframe" }
|
||||
|
||||
|
@ -17,10 +29,4 @@ eframe = { version = "0.16.0", path = "../eframe" }
|
|||
# eframe = { version = "0.16.0", path = "../eframe", default-features = false, features = ["default_fonts", "egui_glium"] }
|
||||
|
||||
egui_demo_lib = { version = "0.16.0", path = "../egui_demo_lib", features = ["extra_debug_asserts"] }
|
||||
|
||||
[features]
|
||||
default = ["persistence"]
|
||||
http = ["egui_demo_lib/http"]
|
||||
persistence = ["eframe/persistence", "egui_demo_lib/persistence"]
|
||||
screen_reader = ["eframe/screen_reader"] # experimental
|
||||
syntax_highlighting = ["egui_demo_lib/syntax_highlighting"]
|
||||
tracing-subscriber = "0.3"
|
||||
|
|
|
@ -7,10 +7,13 @@
|
|||
|
||||
// When compiling natively:
|
||||
fn main() {
|
||||
// Log to stdout (if you run with `RUST_LOG=debug`).
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let app = egui_demo_lib::WrapApp::default();
|
||||
let options = eframe::NativeOptions {
|
||||
// Let's show off that we support transparent windows
|
||||
// transparent: true,
|
||||
transparent: true,
|
||||
drag_and_drop_support: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
|
|
@ -18,22 +18,43 @@ all-features = true
|
|||
|
||||
[lib]
|
||||
|
||||
|
||||
[features]
|
||||
default = ["datetime"]
|
||||
|
||||
# Enable additional checks if debug assertions are enabled (debug builds).
|
||||
extra_debug_asserts = ["egui/extra_debug_asserts"]
|
||||
# Always enable additional checks.
|
||||
extra_asserts = ["egui/extra_asserts"]
|
||||
|
||||
datetime = ["egui_extras/chrono", "chrono"]
|
||||
http = ["ehttp", "image", "poll-promise"]
|
||||
persistence = [
|
||||
"egui/persistence",
|
||||
"epi/persistence",
|
||||
"egui_extras/persistence",
|
||||
"serde",
|
||||
]
|
||||
serialize = ["egui/serialize", "serde"]
|
||||
syntax_highlighting = ["syntect"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false }
|
||||
epi = { version = "0.16.0", path = "../epi" }
|
||||
egui_extras = { version = "0.16.0", path = "../egui_extras" }
|
||||
|
||||
chrono = { version = "0.4", features = ["js-sys", "wasmbind"], optional = true }
|
||||
enum-map = { version = "1", features = ["serde"] }
|
||||
enum-map = { version = "2", features = ["serde"] }
|
||||
unicode_names2 = { version = "0.4.0", default-features = false }
|
||||
|
||||
# feature "http":
|
||||
ehttp = { version = "0.1.0", optional = true }
|
||||
image = { version = "0.23", default-features = false, features = [
|
||||
ehttp = { version = "0.2.0", optional = true }
|
||||
image = { version = "0.24", default-features = false, features = [
|
||||
"jpeg",
|
||||
"png",
|
||||
], optional = true }
|
||||
|
||||
poll-promise = { version = "0.1", default-features = false, optional = true }
|
||||
# feature "syntax_highlighting":
|
||||
syntect = { version = "4", default-features = false, features = [
|
||||
"default-fancy",
|
||||
|
@ -45,25 +66,6 @@ serde = { version = "1", features = ["derive"], optional = true }
|
|||
[dev-dependencies]
|
||||
criterion = { version = "0.3", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["datetime"]
|
||||
datetime = ["egui_extras/datetime", "chrono"]
|
||||
|
||||
# Enable additional checks if debug assertions are enabled (debug builds).
|
||||
extra_debug_asserts = ["egui/extra_debug_asserts"]
|
||||
# Always enable additional checks.
|
||||
extra_asserts = ["egui/extra_asserts"]
|
||||
|
||||
http = ["ehttp", "image"]
|
||||
persistence = [
|
||||
"egui/persistence",
|
||||
"epi/persistence",
|
||||
"egui_extras/persistence",
|
||||
"serde",
|
||||
]
|
||||
serialize = ["egui/serialize", "serde"]
|
||||
syntax_highlighting = ["syntect"]
|
||||
|
||||
[[bench]]
|
||||
name = "benchmark"
|
||||
harness = false
|
||||
|
|
|
@ -4,16 +4,16 @@ use egui::epaint::TextShape;
|
|||
use egui_demo_lib::LOREM_IPSUM_LONG;
|
||||
|
||||
pub fn criterion_benchmark(c: &mut Criterion) {
|
||||
let raw_input = egui::RawInput::default();
|
||||
use egui::RawInput;
|
||||
|
||||
{
|
||||
let mut ctx = egui::CtxRef::default();
|
||||
let ctx = egui::Context::default();
|
||||
let mut demo_windows = egui_demo_lib::DemoWindows::default();
|
||||
|
||||
// The most end-to-end benchmark.
|
||||
c.bench_function("demo_with_tessellate__realistic", |b| {
|
||||
b.iter(|| {
|
||||
let (_output, shapes) = ctx.run(raw_input.clone(), |ctx| {
|
||||
let (_output, shapes) = ctx.run(RawInput::default(), |ctx| {
|
||||
demo_windows.ui(ctx);
|
||||
});
|
||||
ctx.tessellate(shapes)
|
||||
|
@ -22,13 +22,13 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
|||
|
||||
c.bench_function("demo_no_tessellate", |b| {
|
||||
b.iter(|| {
|
||||
ctx.run(raw_input.clone(), |ctx| {
|
||||
ctx.run(RawInput::default(), |ctx| {
|
||||
demo_windows.ui(ctx);
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
let (_output, shapes) = ctx.run(raw_input.clone(), |ctx| {
|
||||
let (_output, shapes) = ctx.run(RawInput::default(), |ctx| {
|
||||
demo_windows.ui(ctx);
|
||||
});
|
||||
c.bench_function("demo_only_tessellate", |b| {
|
||||
|
@ -37,12 +37,12 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
|||
}
|
||||
|
||||
if false {
|
||||
let mut ctx = egui::CtxRef::default();
|
||||
let ctx = egui::Context::default();
|
||||
ctx.memory().set_everything_is_visible(true); // give us everything
|
||||
let mut demo_windows = egui_demo_lib::DemoWindows::default();
|
||||
c.bench_function("demo_full_no_tessellate", |b| {
|
||||
b.iter(|| {
|
||||
ctx.run(raw_input.clone(), |ctx| {
|
||||
ctx.run(RawInput::default(), |ctx| {
|
||||
demo_windows.ui(ctx);
|
||||
})
|
||||
})
|
||||
|
@ -50,8 +50,8 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
|||
}
|
||||
|
||||
{
|
||||
let mut ctx = egui::CtxRef::default();
|
||||
let _ = ctx.run(raw_input, |ctx| {
|
||||
let ctx = egui::Context::default();
|
||||
let _ = ctx.run(RawInput::default(), |ctx| {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
c.bench_function("label &str", |b| {
|
||||
b.iter(|| {
|
||||
|
@ -68,40 +68,68 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
|||
}
|
||||
|
||||
{
|
||||
let pixels_per_point = 1.0;
|
||||
let wrap_width = 512.0;
|
||||
let text_style = egui::TextStyle::Body;
|
||||
let color = egui::Color32::WHITE;
|
||||
let fonts =
|
||||
egui::epaint::text::Fonts::new(pixels_per_point, egui::FontDefinitions::default());
|
||||
c.bench_function("text_layout_uncached", |b| {
|
||||
b.iter(|| {
|
||||
use egui::epaint::text::{layout, LayoutJob};
|
||||
let ctx = egui::Context::default();
|
||||
ctx.begin_frame(RawInput::default());
|
||||
|
||||
let job = LayoutJob::simple(
|
||||
egui::CentralPanel::default().show(&ctx, |ui| {
|
||||
c.bench_function("Painter::rect", |b| {
|
||||
let painter = ui.painter();
|
||||
let rect = ui.max_rect();
|
||||
b.iter(|| {
|
||||
painter.rect(rect, 2.0, egui::Color32::RED, (1.0, egui::Color32::WHITE));
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Don't call `end_frame` to not have to drain the huge paint list
|
||||
}
|
||||
|
||||
{
|
||||
let pixels_per_point = 1.0;
|
||||
let max_texture_side = 8 * 1024;
|
||||
let wrap_width = 512.0;
|
||||
let font_id = egui::FontId::default();
|
||||
let color = egui::Color32::WHITE;
|
||||
let fonts = egui::epaint::text::Fonts::new(
|
||||
pixels_per_point,
|
||||
max_texture_side,
|
||||
egui::FontDefinitions::default(),
|
||||
);
|
||||
{
|
||||
let mut locked_fonts = fonts.lock();
|
||||
c.bench_function("text_layout_uncached", |b| {
|
||||
b.iter(|| {
|
||||
use egui::epaint::text::{layout, LayoutJob};
|
||||
|
||||
let job = LayoutJob::simple(
|
||||
LOREM_IPSUM_LONG.to_owned(),
|
||||
font_id.clone(),
|
||||
color,
|
||||
wrap_width,
|
||||
);
|
||||
layout(&mut locked_fonts.fonts, job.into())
|
||||
})
|
||||
});
|
||||
}
|
||||
c.bench_function("text_layout_cached", |b| {
|
||||
b.iter(|| {
|
||||
fonts.layout(
|
||||
LOREM_IPSUM_LONG.to_owned(),
|
||||
egui::TextStyle::Body,
|
||||
font_id.clone(),
|
||||
color,
|
||||
wrap_width,
|
||||
);
|
||||
layout(&fonts, job.into())
|
||||
)
|
||||
})
|
||||
});
|
||||
c.bench_function("text_layout_cached", |b| {
|
||||
b.iter(|| fonts.layout(LOREM_IPSUM_LONG.to_owned(), text_style, color, wrap_width))
|
||||
});
|
||||
|
||||
let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), text_style, color, wrap_width);
|
||||
let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, color, wrap_width);
|
||||
let mut tessellator = egui::epaint::Tessellator::from_options(Default::default());
|
||||
let mut mesh = egui::epaint::Mesh::default();
|
||||
let text_shape = TextShape::new(egui::Pos2::ZERO, galley);
|
||||
let font_image_size = fonts.font_image_size();
|
||||
c.bench_function("tessellate_text", |b| {
|
||||
b.iter(|| {
|
||||
tessellator.tessellate_text(
|
||||
fonts.font_image().size(),
|
||||
text_shape.clone(),
|
||||
&mut mesh,
|
||||
);
|
||||
tessellator.tessellate_text(font_image_size, text_shape.clone(), &mut mesh);
|
||||
mesh.clear();
|
||||
})
|
||||
});
|
||||
|
|
|
@ -34,7 +34,7 @@ impl epi::App for ColorTest {
|
|||
"🎨 Color test"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if frame.is_web() {
|
||||
ui.label(
|
||||
|
@ -43,14 +43,14 @@ impl epi::App for ColorTest {
|
|||
ui.separator();
|
||||
}
|
||||
ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| {
|
||||
self.ui(ui, Some(frame));
|
||||
self.ui(ui);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorTest {
|
||||
pub fn ui(&mut self, ui: &mut Ui, tex_allocator: Option<&dyn epi::TextureAllocator>) {
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
ui.set_max_width(680.0);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
|
@ -70,13 +70,7 @@ impl ColorTest {
|
|||
ui.spacing_mut().item_spacing.y = 0.0; // No spacing between gradients
|
||||
let g = Gradient::one_color(Color32::from_rgb(255, 165, 0));
|
||||
self.vertex_gradient(ui, "orange rgb(255, 165, 0) - vertex", WHITE, &g);
|
||||
self.tex_gradient(
|
||||
ui,
|
||||
tex_allocator,
|
||||
"orange rgb(255, 165, 0) - texture",
|
||||
WHITE,
|
||||
&g,
|
||||
);
|
||||
self.tex_gradient(ui, "orange rgb(255, 165, 0) - texture", WHITE, &g);
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
@ -99,20 +93,18 @@ impl ColorTest {
|
|||
{
|
||||
let g = Gradient::one_color(Color32::from(tex_color * vertex_color));
|
||||
self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g);
|
||||
self.tex_gradient(ui, tex_allocator, "Ground truth (texture)", WHITE, &g);
|
||||
}
|
||||
if let Some(tex_allocator) = tex_allocator {
|
||||
ui.horizontal(|ui| {
|
||||
let g = Gradient::one_color(Color32::from(tex_color));
|
||||
let tex = self.tex_mngr.get(tex_allocator, &g);
|
||||
let texel_offset = 0.5 / (g.0.len() as f32);
|
||||
let uv =
|
||||
Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
|
||||
ui.add(Image::new(tex, GRADIENT_SIZE).tint(vertex_color).uv(uv))
|
||||
.on_hover_text(format!("A texture that is {} texels wide", g.0.len()));
|
||||
ui.label("GPU result");
|
||||
});
|
||||
self.tex_gradient(ui, "Ground truth (texture)", WHITE, &g);
|
||||
}
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let g = Gradient::one_color(Color32::from(tex_color));
|
||||
let tex = self.tex_mngr.get(ui.ctx(), &g);
|
||||
let texel_offset = 0.5 / (g.0.len() as f32);
|
||||
let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
|
||||
ui.add(Image::new(tex, GRADIENT_SIZE).tint(vertex_color).uv(uv))
|
||||
.on_hover_text(format!("A texture that is {} texels wide", g.0.len()));
|
||||
ui.label("GPU result");
|
||||
});
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
@ -120,18 +112,18 @@ impl ColorTest {
|
|||
// TODO: test color multiplication (image tint),
|
||||
// to make sure vertex and texture color multiplication is done in linear space.
|
||||
|
||||
self.show_gradients(ui, tex_allocator, WHITE, (RED, GREEN));
|
||||
self.show_gradients(ui, WHITE, (RED, GREEN));
|
||||
if self.srgb {
|
||||
ui.label("Notice the darkening in the center of the naive sRGB interpolation.");
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
self.show_gradients(ui, tex_allocator, RED, (TRANSPARENT, GREEN));
|
||||
self.show_gradients(ui, RED, (TRANSPARENT, GREEN));
|
||||
|
||||
ui.separator();
|
||||
|
||||
self.show_gradients(ui, tex_allocator, WHITE, (TRANSPARENT, GREEN));
|
||||
self.show_gradients(ui, WHITE, (TRANSPARENT, GREEN));
|
||||
if self.srgb {
|
||||
ui.label(
|
||||
"Notice how the linear blend stays green while the naive sRGBA interpolation looks gray in the middle.",
|
||||
|
@ -142,15 +134,14 @@ impl ColorTest {
|
|||
|
||||
// TODO: another ground truth where we do the alpha-blending against the background also.
|
||||
// TODO: exactly the same thing, but with vertex colors (no textures)
|
||||
self.show_gradients(ui, tex_allocator, WHITE, (TRANSPARENT, BLACK));
|
||||
self.show_gradients(ui, WHITE, (TRANSPARENT, BLACK));
|
||||
ui.separator();
|
||||
self.show_gradients(ui, tex_allocator, BLACK, (TRANSPARENT, WHITE));
|
||||
self.show_gradients(ui, BLACK, (TRANSPARENT, WHITE));
|
||||
ui.separator();
|
||||
|
||||
ui.label("Additive blending: add more and more blue to the red background:");
|
||||
self.show_gradients(
|
||||
ui,
|
||||
tex_allocator,
|
||||
RED,
|
||||
(TRANSPARENT, Color32::from_rgb_additive(0, 0, 255)),
|
||||
);
|
||||
|
@ -160,13 +151,7 @@ impl ColorTest {
|
|||
pixel_test(ui);
|
||||
}
|
||||
|
||||
fn show_gradients(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
tex_allocator: Option<&dyn epi::TextureAllocator>,
|
||||
bg_fill: Color32,
|
||||
(left, right): (Color32, Color32),
|
||||
) {
|
||||
fn show_gradients(&mut self, ui: &mut Ui, bg_fill: Color32, (left, right): (Color32, Color32)) {
|
||||
let is_opaque = left.is_opaque() && right.is_opaque();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
|
@ -186,13 +171,7 @@ impl ColorTest {
|
|||
if is_opaque {
|
||||
let g = Gradient::ground_truth_linear_gradient(left, right);
|
||||
self.vertex_gradient(ui, "Ground Truth (CPU gradient) - vertices", bg_fill, &g);
|
||||
self.tex_gradient(
|
||||
ui,
|
||||
tex_allocator,
|
||||
"Ground Truth (CPU gradient) - texture",
|
||||
bg_fill,
|
||||
&g,
|
||||
);
|
||||
self.tex_gradient(ui, "Ground Truth (CPU gradient) - texture", bg_fill, &g);
|
||||
} else {
|
||||
let g = Gradient::ground_truth_linear_gradient(left, right).with_bg_fill(bg_fill);
|
||||
self.vertex_gradient(
|
||||
|
@ -203,20 +182,13 @@ impl ColorTest {
|
|||
);
|
||||
self.tex_gradient(
|
||||
ui,
|
||||
tex_allocator,
|
||||
"Ground Truth (CPU gradient, CPU blending) - texture",
|
||||
bg_fill,
|
||||
&g,
|
||||
);
|
||||
let g = Gradient::ground_truth_linear_gradient(left, right);
|
||||
self.vertex_gradient(ui, "CPU gradient, GPU blending - vertices", bg_fill, &g);
|
||||
self.tex_gradient(
|
||||
ui,
|
||||
tex_allocator,
|
||||
"CPU gradient, GPU blending - texture",
|
||||
bg_fill,
|
||||
&g,
|
||||
);
|
||||
self.tex_gradient(ui, "CPU gradient, GPU blending - texture", bg_fill, &g);
|
||||
}
|
||||
|
||||
let g = Gradient::texture_gradient(left, right);
|
||||
|
@ -226,13 +198,7 @@ impl ColorTest {
|
|||
bg_fill,
|
||||
&g,
|
||||
);
|
||||
self.tex_gradient(
|
||||
ui,
|
||||
tex_allocator,
|
||||
"Texture of width 2 (test texture sampler)",
|
||||
bg_fill,
|
||||
&g,
|
||||
);
|
||||
self.tex_gradient(ui, "Texture of width 2 (test texture sampler)", bg_fill, &g);
|
||||
|
||||
if self.srgb {
|
||||
let g =
|
||||
|
@ -243,41 +209,26 @@ impl ColorTest {
|
|||
bg_fill,
|
||||
&g,
|
||||
);
|
||||
self.tex_gradient(
|
||||
ui,
|
||||
tex_allocator,
|
||||
"Naive sRGBA interpolation (WRONG)",
|
||||
bg_fill,
|
||||
&g,
|
||||
);
|
||||
self.tex_gradient(ui, "Naive sRGBA interpolation (WRONG)", bg_fill, &g);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn tex_gradient(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
tex_allocator: Option<&dyn epi::TextureAllocator>,
|
||||
label: &str,
|
||||
bg_fill: Color32,
|
||||
gradient: &Gradient,
|
||||
) {
|
||||
fn tex_gradient(&mut self, ui: &mut Ui, label: &str, bg_fill: Color32, gradient: &Gradient) {
|
||||
if !self.texture_gradients {
|
||||
return;
|
||||
}
|
||||
if let Some(tex_allocator) = tex_allocator {
|
||||
ui.horizontal(|ui| {
|
||||
let tex = self.tex_mngr.get(tex_allocator, gradient);
|
||||
let texel_offset = 0.5 / (gradient.0.len() as f32);
|
||||
let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
|
||||
ui.add(Image::new(tex, GRADIENT_SIZE).bg_fill(bg_fill).uv(uv))
|
||||
.on_hover_text(format!(
|
||||
"A texture that is {} texels wide",
|
||||
gradient.0.len()
|
||||
));
|
||||
ui.label(label);
|
||||
});
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
let tex = self.tex_mngr.get(ui.ctx(), gradient);
|
||||
let texel_offset = 0.5 / (gradient.0.len() as f32);
|
||||
let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
|
||||
ui.add(Image::new(tex, GRADIENT_SIZE).bg_fill(bg_fill).uv(uv))
|
||||
.on_hover_text(format!(
|
||||
"A texture that is {} texels wide",
|
||||
gradient.0.len()
|
||||
));
|
||||
ui.label(label);
|
||||
});
|
||||
}
|
||||
|
||||
fn vertex_gradient(&mut self, ui: &mut Ui, label: &str, bg_fill: Color32, gradient: &Gradient) {
|
||||
|
@ -384,18 +335,21 @@ impl Gradient {
|
|||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TextureManager(HashMap<Gradient, TextureId>);
|
||||
struct TextureManager(HashMap<Gradient, TextureHandle>);
|
||||
|
||||
impl TextureManager {
|
||||
fn get(&mut self, tex_allocator: &dyn epi::TextureAllocator, gradient: &Gradient) -> TextureId {
|
||||
*self.0.entry(gradient.clone()).or_insert_with(|| {
|
||||
fn get(&mut self, ctx: &egui::Context, gradient: &Gradient) -> &TextureHandle {
|
||||
self.0.entry(gradient.clone()).or_insert_with(|| {
|
||||
let pixels = gradient.to_pixel_row();
|
||||
let width = pixels.len();
|
||||
let height = 1;
|
||||
tex_allocator.alloc(epi::Image {
|
||||
size: [width, height],
|
||||
pixels,
|
||||
})
|
||||
ctx.load_texture(
|
||||
"color_test_gradient",
|
||||
epaint::ColorImage {
|
||||
size: [width, height],
|
||||
pixels,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ impl epi::App for DemoApp {
|
|||
|
||||
fn setup(
|
||||
&mut self,
|
||||
_ctx: &egui::CtxRef,
|
||||
_ctx: &egui::Context,
|
||||
_frame: &epi::Frame,
|
||||
_storage: Option<&dyn epi::Storage>,
|
||||
) {
|
||||
|
@ -31,7 +31,7 @@ impl epi::App for DemoApp {
|
|||
epi::set_value(storage, epi::APP_KEY, self);
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||
self.demo_windows.ui(ctx);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ impl super::Demo for CodeEditor {
|
|||
"🖮 Code Editor"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
use super::View as _;
|
||||
egui::Window::new(self.name())
|
||||
.open(open)
|
||||
|
@ -71,7 +71,7 @@ impl super::View for CodeEditor {
|
|||
ui.collapsing("Theme", |ui| {
|
||||
ui.group(|ui| {
|
||||
theme.ui(ui);
|
||||
theme.store_in_memory(ui.ctx());
|
||||
theme.clone().store_in_memory(ui.ctx());
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -85,7 +85,7 @@ impl super::View for CodeEditor {
|
|||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(code)
|
||||
.text_style(egui::TextStyle::Monospace) // for cursor height
|
||||
.font(egui::TextStyle::Monospace) // for cursor height
|
||||
.code_editor()
|
||||
.desired_rows(10)
|
||||
.lock_focus(true)
|
||||
|
|
|
@ -68,7 +68,7 @@ impl super::Demo for CodeExample {
|
|||
"🖮 Code Example"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
use super::View;
|
||||
egui::Window::new(self.name())
|
||||
.open(open)
|
||||
|
@ -98,7 +98,8 @@ impl CodeExample {
|
|||
);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let indentation = 8.0 * ui.fonts()[egui::TextStyle::Monospace].glyph_width(' ');
|
||||
let font_id = egui::TextStyle::Monospace.resolve(ui.style());
|
||||
let indentation = 8.0 * ui.fonts().glyph_width(&font_id, ' ');
|
||||
let item_spacing = ui.spacing_mut().item_spacing;
|
||||
ui.add_space(indentation - item_spacing.x);
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ impl super::Demo for ContextMenus {
|
|||
"☰ Context Menus"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
use super::View;
|
||||
egui::Window::new(self.name())
|
||||
.vscroll(false)
|
||||
|
|
|
@ -10,7 +10,7 @@ impl super::Demo for DancingStrings {
|
|||
"♫ Dancing Strings"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
|
||||
fn show(&mut self, ctx: &Context, open: &mut bool) {
|
||||
use super::View as _;
|
||||
Window::new(self.name())
|
||||
.open(open)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::Demo;
|
||||
use egui::{CtxRef, ScrollArea, Ui};
|
||||
use egui::{Context, ScrollArea, Ui};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -26,6 +26,7 @@ impl Default for Demos {
|
|||
Box::new(super::MiscDemoWindow::default()),
|
||||
Box::new(super::multi_touch::MultiTouch::default()),
|
||||
Box::new(super::painting::Painting::default()),
|
||||
Box::new(super::paint_bezier::PaintBezier::default()),
|
||||
Box::new(super::plot_demo::PlotDemo::default()),
|
||||
Box::new(super::scrolling::Scrolling::default()),
|
||||
Box::new(super::sliders::Sliders::default()),
|
||||
|
@ -60,7 +61,7 @@ impl Demos {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn windows(&mut self, ctx: &CtxRef) {
|
||||
pub fn windows(&mut self, ctx: &Context) {
|
||||
let Self { demos, open } = self;
|
||||
for demo in demos {
|
||||
let mut is_open = open.contains(demo.name());
|
||||
|
@ -115,7 +116,7 @@ impl Tests {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn windows(&mut self, ctx: &CtxRef) {
|
||||
pub fn windows(&mut self, ctx: &Context) {
|
||||
let Self { demos, open } = self;
|
||||
for demo in demos {
|
||||
let mut is_open = open.contains(demo.name());
|
||||
|
@ -151,7 +152,7 @@ pub struct DemoWindows {
|
|||
impl DemoWindows {
|
||||
/// Show the app ui (menu bar and windows).
|
||||
/// `sidebar_ui` can be used to optionally show some things in the sidebar
|
||||
pub fn ui(&mut self, ctx: &CtxRef) {
|
||||
pub fn ui(&mut self, ctx: &Context) {
|
||||
let Self { demos, tests } = self;
|
||||
|
||||
egui::SidePanel::right("egui_demo_panel")
|
||||
|
@ -216,7 +217,7 @@ impl DemoWindows {
|
|||
}
|
||||
|
||||
/// Show the open windows.
|
||||
fn windows(&mut self, ctx: &CtxRef) {
|
||||
fn windows(&mut self, ctx: &Context) {
|
||||
let Self { demos, tests } = self;
|
||||
|
||||
demos.windows(ctx);
|
||||
|
|
|
@ -25,7 +25,7 @@ pub fn drag_source(ui: &mut Ui, id: Id, body: impl FnOnce(&mut Ui)) {
|
|||
// (anything with `Order::Tooltip` always gets an empty `Response`)
|
||||
// So this is fine!
|
||||
|
||||
if let Some(pointer_pos) = ui.input().pointer.interact_pos() {
|
||||
if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
|
||||
let delta = pointer_pos - response.rect.center();
|
||||
ui.ctx().translate_layer(layer_id, delta);
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ pub fn drop_target<R>(
|
|||
ui.painter().set(
|
||||
where_to_put_background,
|
||||
epaint::RectShape {
|
||||
corner_radius: style.corner_radius,
|
||||
rounding: style.rounding,
|
||||
fill,
|
||||
stroke,
|
||||
rect,
|
||||
|
@ -101,7 +101,7 @@ impl super::Demo for DragAndDropDemo {
|
|||
"✋ Drag and Drop"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
|
||||
fn show(&mut self, ctx: &Context, open: &mut bool) {
|
||||
use super::View as _;
|
||||
Window::new(self.name())
|
||||
.open(open)
|
||||
|
|
|
@ -2,15 +2,15 @@ use std::collections::BTreeMap;
|
|||
|
||||
pub struct FontBook {
|
||||
filter: String,
|
||||
text_style: egui::TextStyle,
|
||||
named_chars: BTreeMap<egui::TextStyle, BTreeMap<char, String>>,
|
||||
font_id: egui::FontId,
|
||||
named_chars: BTreeMap<egui::FontFamily, BTreeMap<char, String>>,
|
||||
}
|
||||
|
||||
impl Default for FontBook {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
filter: Default::default(),
|
||||
text_style: egui::TextStyle::Button,
|
||||
font_id: egui::FontId::proportional(20.0),
|
||||
named_chars: Default::default(),
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ impl super::Demo for FontBook {
|
|||
"🔤 Font Book"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name()).open(open).show(ctx, |ui| {
|
||||
use super::View as _;
|
||||
self.ui(ui);
|
||||
|
@ -34,7 +34,7 @@ impl super::View for FontBook {
|
|||
ui.label(format!(
|
||||
"The selected font supports {} characters.",
|
||||
self.named_chars
|
||||
.get(&self.text_style)
|
||||
.get(&self.font_id.family)
|
||||
.map(|map| map.len())
|
||||
.unwrap_or_default()
|
||||
));
|
||||
|
@ -51,13 +51,7 @@ impl super::View for FontBook {
|
|||
|
||||
ui.separator();
|
||||
|
||||
egui::ComboBox::from_label("Text style")
|
||||
.selected_text(format!("{:?}", self.text_style))
|
||||
.show_ui(ui, |ui| {
|
||||
for style in egui::TextStyle::all() {
|
||||
ui.selectable_value(&mut self.text_style, style, format!("{:?}", style));
|
||||
}
|
||||
});
|
||||
egui::introspection::font_id_ui(ui, &mut self.font_id);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Filter:");
|
||||
|
@ -68,16 +62,11 @@ impl super::View for FontBook {
|
|||
}
|
||||
});
|
||||
|
||||
let text_style = self.text_style;
|
||||
let filter = &self.filter;
|
||||
let named_chars = self.named_chars.entry(text_style).or_insert_with(|| {
|
||||
ui.fonts()[text_style]
|
||||
.characters()
|
||||
.iter()
|
||||
.filter(|chr| !chr.is_whitespace() && !chr.is_ascii_control())
|
||||
.map(|&chr| (chr, char_name(chr)))
|
||||
.collect()
|
||||
});
|
||||
let named_chars = self
|
||||
.named_chars
|
||||
.entry(self.font_id.family.clone())
|
||||
.or_insert_with(|| available_characters(ui, self.font_id.family.clone()));
|
||||
|
||||
ui.separator();
|
||||
|
||||
|
@ -88,12 +77,14 @@ impl super::View for FontBook {
|
|||
for (&chr, name) in named_chars {
|
||||
if filter.is_empty() || name.contains(filter) || *filter == chr.to_string() {
|
||||
let button = egui::Button::new(
|
||||
egui::RichText::new(chr.to_string()).text_style(text_style),
|
||||
egui::RichText::new(chr.to_string()).font(self.font_id.clone()),
|
||||
)
|
||||
.frame(false);
|
||||
|
||||
let tooltip_ui = |ui: &mut egui::Ui| {
|
||||
ui.label(egui::RichText::new(chr.to_string()).text_style(text_style));
|
||||
ui.label(
|
||||
egui::RichText::new(chr.to_string()).font(self.font_id.clone()),
|
||||
);
|
||||
ui.label(format!("{}\nU+{:X}\n\nClick to copy", name, chr as u32));
|
||||
};
|
||||
|
||||
|
@ -107,6 +98,18 @@ impl super::View for FontBook {
|
|||
}
|
||||
}
|
||||
|
||||
fn available_characters(ui: &egui::Ui, family: egui::FontFamily) -> BTreeMap<char, String> {
|
||||
ui.fonts()
|
||||
.lock()
|
||||
.fonts
|
||||
.font(&egui::FontId::new(10.0, family)) // size is arbitrary for getting the characters
|
||||
.characters()
|
||||
.iter()
|
||||
.filter(|chr| !chr.is_whitespace() && !chr.is_ascii_control())
|
||||
.map(|&chr| (chr, char_name(chr)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn char_name(chr: char) -> String {
|
||||
special_char_name(chr)
|
||||
.map(|s| s.to_owned())
|
||||
|
|
|
@ -11,7 +11,7 @@ impl super::Demo for GridDemo {
|
|||
"▣ Grid Demo"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name())
|
||||
.open(open)
|
||||
.resizable(true)
|
||||
|
|
|
@ -76,7 +76,7 @@ impl super::Demo for LayoutTest {
|
|||
"Layout Test"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name())
|
||||
.open(open)
|
||||
.resizable(false)
|
||||
|
|
|
@ -31,7 +31,7 @@ impl Demo for MiscDemoWindow {
|
|||
"✨ Misc Demos"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
|
||||
fn show(&mut self, ctx: &Context, open: &mut bool) {
|
||||
Window::new(self.name())
|
||||
.open(open)
|
||||
.vscroll(true)
|
||||
|
@ -140,7 +140,8 @@ impl Widgets {
|
|||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
// Trick so we don't have to add spaces in the text below:
|
||||
ui.spacing_mut().item_spacing.x = ui.fonts()[TextStyle::Body].glyph_width(' ');
|
||||
let width = ui.fonts().glyph_width(&TextStyle::Body.resolve(ui.style()), ' ');
|
||||
ui.spacing_mut().item_spacing.x = width;
|
||||
|
||||
ui.label(RichText::new("Text can have").color(Color32::from_rgb(110, 255, 110)));
|
||||
ui.colored_label(Color32::from_rgb(128, 140, 255), "color"); // Shortcut version
|
||||
|
@ -268,7 +269,7 @@ impl ColorWidgets {
|
|||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
struct BoxPainting {
|
||||
size: Vec2,
|
||||
corner_radius: f32,
|
||||
rounding: f32,
|
||||
stroke_width: f32,
|
||||
num_boxes: usize,
|
||||
}
|
||||
|
@ -277,7 +278,7 @@ impl Default for BoxPainting {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
size: vec2(64.0, 32.0),
|
||||
corner_radius: 5.0,
|
||||
rounding: 5.0,
|
||||
stroke_width: 2.0,
|
||||
num_boxes: 1,
|
||||
}
|
||||
|
@ -288,7 +289,7 @@ impl BoxPainting {
|
|||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
ui.add(Slider::new(&mut self.size.x, 0.0..=500.0).text("width"));
|
||||
ui.add(Slider::new(&mut self.size.y, 0.0..=500.0).text("height"));
|
||||
ui.add(Slider::new(&mut self.corner_radius, 0.0..=50.0).text("corner_radius"));
|
||||
ui.add(Slider::new(&mut self.rounding, 0.0..=50.0).text("rounding"));
|
||||
ui.add(Slider::new(&mut self.stroke_width, 0.0..=10.0).text("stroke_width"));
|
||||
ui.add(Slider::new(&mut self.num_boxes, 0..=8).text("num_boxes"));
|
||||
|
||||
|
@ -297,7 +298,7 @@ impl BoxPainting {
|
|||
let (rect, _response) = ui.allocate_at_least(self.size, Sense::hover());
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
self.corner_radius,
|
||||
self.rounding,
|
||||
Color32::from_gray(64),
|
||||
Stroke::new(self.stroke_width, Color32::WHITE),
|
||||
);
|
||||
|
@ -417,7 +418,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
"This is a demonstration of ",
|
||||
first_row_indentation,
|
||||
TextFormat {
|
||||
style: TextStyle::Body,
|
||||
color: default_color,
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -426,7 +426,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
"the egui text layout engine. ",
|
||||
0.0,
|
||||
TextFormat {
|
||||
style: TextStyle::Body,
|
||||
color: strong_color,
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -435,7 +434,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
"It supports ",
|
||||
0.0,
|
||||
TextFormat {
|
||||
style: TextStyle::Body,
|
||||
color: default_color,
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -444,7 +442,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
"different ",
|
||||
0.0,
|
||||
TextFormat {
|
||||
style: TextStyle::Body,
|
||||
color: Color32::from_rgb(110, 255, 110),
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -453,7 +450,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
"colors, ",
|
||||
0.0,
|
||||
TextFormat {
|
||||
style: TextStyle::Body,
|
||||
color: Color32::from_rgb(128, 140, 255),
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -462,7 +458,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
"backgrounds, ",
|
||||
0.0,
|
||||
TextFormat {
|
||||
style: TextStyle::Body,
|
||||
color: default_color,
|
||||
background: Color32::from_rgb(128, 32, 32),
|
||||
..Default::default()
|
||||
|
@ -472,7 +467,7 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
"mixing ",
|
||||
0.0,
|
||||
TextFormat {
|
||||
style: TextStyle::Heading,
|
||||
font_id: FontId::proportional(20.0),
|
||||
color: default_color,
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -481,7 +476,7 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
"fonts, ",
|
||||
0.0,
|
||||
TextFormat {
|
||||
style: TextStyle::Monospace,
|
||||
font_id: FontId::monospace(14.0),
|
||||
color: default_color,
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -490,7 +485,7 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
"raised text, ",
|
||||
0.0,
|
||||
TextFormat {
|
||||
style: TextStyle::Small,
|
||||
font_id: FontId::proportional(8.0),
|
||||
color: default_color,
|
||||
valign: Align::TOP,
|
||||
..Default::default()
|
||||
|
@ -500,7 +495,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
"with ",
|
||||
0.0,
|
||||
TextFormat {
|
||||
style: TextStyle::Body,
|
||||
color: default_color,
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -509,7 +503,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
"underlining",
|
||||
0.0,
|
||||
TextFormat {
|
||||
style: TextStyle::Body,
|
||||
color: default_color,
|
||||
underline: Stroke::new(1.0, Color32::LIGHT_BLUE),
|
||||
..Default::default()
|
||||
|
@ -519,7 +512,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
" and ",
|
||||
0.0,
|
||||
TextFormat {
|
||||
style: TextStyle::Body,
|
||||
color: default_color,
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -528,7 +520,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
"strikethrough",
|
||||
0.0,
|
||||
TextFormat {
|
||||
style: TextStyle::Body,
|
||||
color: default_color,
|
||||
strikethrough: Stroke::new(2.0, Color32::RED.linear_multiply(0.5)),
|
||||
..Default::default()
|
||||
|
@ -538,7 +529,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
". Of course, ",
|
||||
0.0,
|
||||
TextFormat {
|
||||
style: TextStyle::Body,
|
||||
color: default_color,
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -547,7 +537,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
"you can",
|
||||
0.0,
|
||||
TextFormat {
|
||||
style: TextStyle::Body,
|
||||
color: default_color,
|
||||
strikethrough: Stroke::new(1.0, strong_color),
|
||||
..Default::default()
|
||||
|
@ -557,7 +546,7 @@ fn text_layout_ui(ui: &mut egui::Ui) {
|
|||
" mix these!",
|
||||
0.0,
|
||||
TextFormat {
|
||||
style: TextStyle::Small,
|
||||
font_id: FontId::proportional(8.0),
|
||||
color: Color32::LIGHT_BLUE,
|
||||
background: Color32::from_rgb(128, 0, 0),
|
||||
underline: Stroke::new(1.0, strong_color),
|
||||
|
|
|
@ -16,6 +16,7 @@ pub mod grid_demo;
|
|||
pub mod layout_test;
|
||||
pub mod misc_demo_window;
|
||||
pub mod multi_touch;
|
||||
pub mod paint_bezier;
|
||||
pub mod painting;
|
||||
pub mod password;
|
||||
pub mod plot_demo;
|
||||
|
@ -47,5 +48,5 @@ pub trait Demo {
|
|||
fn name(&self) -> &'static str;
|
||||
|
||||
/// Show windows, etc
|
||||
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool);
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool);
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ impl super::Demo for MultiTouch {
|
|||
"👌 Multi Touch"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name())
|
||||
.open(open)
|
||||
.default_size(vec2(512.0, 512.0))
|
||||
|
@ -77,7 +77,7 @@ impl super::View for MultiTouch {
|
|||
// color and width:
|
||||
let mut stroke_width = 1.;
|
||||
let color = Color32::GRAY;
|
||||
if let Some(multi_touch) = ui.input().multi_touch() {
|
||||
if let Some(multi_touch) = ui.ctx().multi_touch() {
|
||||
// This adjusts the current zoom factor and rotation angle according to the dynamic
|
||||
// change (for the current frame) of the touch gesture:
|
||||
self.zoom *= multi_touch.zoom_delta;
|
||||
|
|
254
egui_demo_lib/src/apps/demo/paint_bezier.rs
Normal file
254
egui_demo_lib/src/apps/demo/paint_bezier.rs
Normal file
|
@ -0,0 +1,254 @@
|
|||
use egui::emath::RectTransform;
|
||||
use egui::epaint::{CircleShape, CubicBezierShape, QuadraticBezierShape};
|
||||
use egui::*;
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct PaintBezier {
|
||||
/// Current bezier curve degree, it can be 3, 4.
|
||||
bezier: usize,
|
||||
/// Track the bezier degree before change in order to clean the remaining points.
|
||||
degree_backup: usize,
|
||||
/// Points already clicked. once it reaches the 'bezier' degree, it will be pushed into the 'shapes'
|
||||
points: Vec<Pos2>,
|
||||
/// Track last points set in order to draw auxiliary lines.
|
||||
backup_points: Vec<Pos2>,
|
||||
/// Quadratic shapes already drawn.
|
||||
q_shapes: Vec<QuadraticBezierShape>,
|
||||
/// Cubic shapes already drawn.
|
||||
/// Since `Shape` can't be 'serialized', we can't use Shape as variable type.
|
||||
c_shapes: Vec<CubicBezierShape>,
|
||||
/// Stroke for auxiliary lines.
|
||||
aux_stroke: Stroke,
|
||||
/// Stroke for bezier curve.
|
||||
stroke: Stroke,
|
||||
/// Fill for bezier curve.
|
||||
fill: Color32,
|
||||
/// The curve should be closed or not.
|
||||
closed: bool,
|
||||
/// Display the bounding box or not.
|
||||
show_bounding_box: bool,
|
||||
/// Storke for the bounding box.
|
||||
bounding_box_stroke: Stroke,
|
||||
}
|
||||
|
||||
impl Default for PaintBezier {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bezier: 4, // default bezier degree, a cubic bezier curve
|
||||
degree_backup: 4,
|
||||
points: Default::default(),
|
||||
backup_points: Default::default(),
|
||||
q_shapes: Default::default(),
|
||||
c_shapes: Default::default(),
|
||||
aux_stroke: Stroke::new(1.0, Color32::RED),
|
||||
stroke: Stroke::new(1.0, Color32::LIGHT_BLUE),
|
||||
fill: Default::default(),
|
||||
closed: false,
|
||||
show_bounding_box: false,
|
||||
bounding_box_stroke: Stroke::new(1.0, Color32::LIGHT_GREEN),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PaintBezier {
|
||||
pub fn ui_control(&mut self, ui: &mut egui::Ui) -> egui::Response {
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
egui::stroke_ui(ui, &mut self.stroke, "Curve Stroke");
|
||||
egui::stroke_ui(ui, &mut self.aux_stroke, "Auxiliary Stroke");
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Fill Color:");
|
||||
if ui.color_edit_button_srgba(&mut self.fill).changed()
|
||||
&& self.fill != Color32::TRANSPARENT
|
||||
{
|
||||
self.closed = true;
|
||||
}
|
||||
if ui.checkbox(&mut self.closed, "Closed").clicked() && !self.closed {
|
||||
self.fill = Color32::TRANSPARENT;
|
||||
}
|
||||
});
|
||||
egui::stroke_ui(ui, &mut self.bounding_box_stroke, "Bounding Box Stroke");
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
ui.vertical(|ui| {
|
||||
{
|
||||
let mut tessellation_options = *(ui.ctx().tessellation_options());
|
||||
let tessellation_options = &mut tessellation_options;
|
||||
tessellation_options.ui(ui);
|
||||
let mut new_tessellation_options = ui.ctx().tessellation_options();
|
||||
*new_tessellation_options = *tessellation_options;
|
||||
}
|
||||
|
||||
ui.checkbox(&mut self.show_bounding_box, "Bounding Box");
|
||||
});
|
||||
ui.separator();
|
||||
ui.vertical(|ui| {
|
||||
if ui.radio_value(&mut self.bezier, 3, "Quadratic").clicked()
|
||||
&& self.degree_backup != self.bezier
|
||||
{
|
||||
self.points.clear();
|
||||
self.degree_backup = self.bezier;
|
||||
};
|
||||
if ui.radio_value(&mut self.bezier, 4, "Cubic").clicked()
|
||||
&& self.degree_backup != self.bezier
|
||||
{
|
||||
self.points.clear();
|
||||
self.degree_backup = self.bezier;
|
||||
};
|
||||
// ui.radio_value(self.bezier, 5, "Quintic");
|
||||
ui.label("Click 3 or 4 points to build a bezier curve!");
|
||||
if ui.button("Clear Painting").clicked() {
|
||||
self.points.clear();
|
||||
self.backup_points.clear();
|
||||
self.q_shapes.clear();
|
||||
self.c_shapes.clear();
|
||||
}
|
||||
})
|
||||
})
|
||||
.response
|
||||
}
|
||||
|
||||
pub fn ui_content(&mut self, ui: &mut Ui) -> egui::Response {
|
||||
let (mut response, painter) =
|
||||
ui.allocate_painter(ui.available_size_before_wrap(), Sense::click());
|
||||
|
||||
let to_screen = emath::RectTransform::from_to(
|
||||
Rect::from_min_size(Pos2::ZERO, response.rect.square_proportions()),
|
||||
response.rect,
|
||||
);
|
||||
let from_screen = to_screen.inverse();
|
||||
|
||||
if response.clicked() {
|
||||
if let Some(pointer_pos) = response.interact_pointer_pos() {
|
||||
let canvas_pos = from_screen * pointer_pos;
|
||||
self.points.push(canvas_pos);
|
||||
if self.points.len() >= self.bezier {
|
||||
self.backup_points = self.points.clone();
|
||||
let points = self.points.drain(..).collect::<Vec<_>>();
|
||||
match points.len() {
|
||||
3 => {
|
||||
let quadratic = QuadraticBezierShape::from_points_stroke(
|
||||
points,
|
||||
self.closed,
|
||||
self.fill,
|
||||
self.stroke,
|
||||
);
|
||||
self.q_shapes.push(quadratic);
|
||||
}
|
||||
4 => {
|
||||
let cubic = CubicBezierShape::from_points_stroke(
|
||||
points,
|
||||
self.closed,
|
||||
self.fill,
|
||||
self.stroke,
|
||||
);
|
||||
self.c_shapes.push(cubic);
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response.mark_changed();
|
||||
}
|
||||
}
|
||||
let mut shapes = Vec::new();
|
||||
for shape in self.q_shapes.iter() {
|
||||
shapes.push(shape.to_screen(&to_screen).into());
|
||||
if self.show_bounding_box {
|
||||
shapes.push(self.build_bounding_box(shape.bounding_rect(), &to_screen));
|
||||
}
|
||||
}
|
||||
for shape in self.c_shapes.iter() {
|
||||
shapes.push(shape.to_screen(&to_screen).into());
|
||||
if self.show_bounding_box {
|
||||
shapes.push(self.build_bounding_box(shape.bounding_rect(), &to_screen));
|
||||
}
|
||||
}
|
||||
painter.extend(shapes);
|
||||
|
||||
if !self.points.is_empty() {
|
||||
painter.extend(build_auxiliary_line(
|
||||
&self.points,
|
||||
&to_screen,
|
||||
&self.aux_stroke,
|
||||
));
|
||||
} else if !self.backup_points.is_empty() {
|
||||
painter.extend(build_auxiliary_line(
|
||||
&self.backup_points,
|
||||
&to_screen,
|
||||
&self.aux_stroke,
|
||||
));
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
pub fn build_bounding_box(&self, bbox: Rect, to_screen: &RectTransform) -> Shape {
|
||||
let bbox = Rect {
|
||||
min: to_screen * bbox.min,
|
||||
max: to_screen * bbox.max,
|
||||
};
|
||||
let bbox_shape = epaint::RectShape::stroke(bbox, 0.0, self.bounding_box_stroke);
|
||||
bbox_shape.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// An internal function to create auxiliary lines around the current bezier curve
|
||||
/// or to auxiliary lines (points) before the points meet the bezier curve requirements.
|
||||
fn build_auxiliary_line(
|
||||
points: &[Pos2],
|
||||
to_screen: &RectTransform,
|
||||
aux_stroke: &Stroke,
|
||||
) -> Vec<Shape> {
|
||||
let mut shapes = Vec::new();
|
||||
if points.len() >= 2 {
|
||||
let points: Vec<Pos2> = points.iter().map(|p| to_screen * *p).collect();
|
||||
shapes.push(egui::Shape::line(points, *aux_stroke));
|
||||
}
|
||||
for point in points.iter() {
|
||||
let center = to_screen * *point;
|
||||
let radius = aux_stroke.width * 3.0;
|
||||
let circle = CircleShape {
|
||||
center,
|
||||
radius,
|
||||
fill: aux_stroke.color,
|
||||
stroke: *aux_stroke,
|
||||
};
|
||||
|
||||
shapes.push(circle.into());
|
||||
}
|
||||
|
||||
shapes
|
||||
}
|
||||
|
||||
impl super::Demo for PaintBezier {
|
||||
fn name(&self) -> &'static str {
|
||||
"✔ Bezier Curve"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &Context, open: &mut bool) {
|
||||
use super::View as _;
|
||||
Window::new(self.name())
|
||||
.open(open)
|
||||
.default_size(vec2(512.0, 512.0))
|
||||
.vscroll(false)
|
||||
.show(ctx, |ui| self.ui(ui));
|
||||
}
|
||||
}
|
||||
|
||||
impl super::View for PaintBezier {
|
||||
fn ui(&mut self, ui: &mut Ui) {
|
||||
// ui.vertical_centered(|ui| {
|
||||
// ui.add(crate::__egui_github_link_file!());
|
||||
// });
|
||||
self.ui_control(ui);
|
||||
|
||||
Frame::dark_canvas(ui.style()).show(ui, |ui| {
|
||||
self.ui_content(ui);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -74,7 +74,7 @@ impl super::Demo for Painting {
|
|||
"🖊 Painting"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
|
||||
fn show(&mut self, ctx: &Context, open: &mut bool) {
|
||||
use super::View as _;
|
||||
Window::new(self.name())
|
||||
.open(open)
|
||||
|
|
|
@ -19,7 +19,7 @@ pub fn password_ui(ui: &mut egui::Ui, password: &mut String) -> egui::Response {
|
|||
|
||||
// Get state for this widget.
|
||||
// You should get state by value, not by reference to avoid borrowing of `Memory`.
|
||||
let mut show_plaintext = ui.memory().data.get_temp::<bool>(state_id).unwrap_or(false);
|
||||
let mut show_plaintext = ui.data().get_temp::<bool>(state_id).unwrap_or(false);
|
||||
|
||||
// Process ui, change a local copy of the state
|
||||
// We want TextEdit to fill entire space, and have button after that, so in that case we can
|
||||
|
@ -42,7 +42,7 @@ pub fn password_ui(ui: &mut egui::Ui, password: &mut String) -> egui::Response {
|
|||
});
|
||||
|
||||
// Store the (possibly changed) state:
|
||||
ui.memory().data.insert_temp(state_id, show_plaintext);
|
||||
ui.data().insert_temp(state_id, show_plaintext);
|
||||
|
||||
// All done! Return the interaction response so the user can check what happened
|
||||
// (hovered, clicked, …) and maybe show a tooltip:
|
||||
|
|
|
@ -71,8 +71,8 @@ impl LineDemo {
|
|||
|
||||
ui.vertical(|ui| {
|
||||
ui.style_mut().wrap = Some(false);
|
||||
ui.checkbox(animate, "animate");
|
||||
ui.checkbox(square, "square view")
|
||||
ui.checkbox(animate, "Animate");
|
||||
ui.checkbox(square, "Square view")
|
||||
.on_hover_text("Always keep the viewport square.");
|
||||
ui.checkbox(proportional, "Proportional data axes")
|
||||
.on_hover_text("Tick are the same size on both axes.");
|
||||
|
@ -237,19 +237,11 @@ impl Widget for &mut MarkerDemo {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Default, PartialEq)]
|
||||
struct LegendDemo {
|
||||
config: Legend,
|
||||
}
|
||||
|
||||
impl Default for LegendDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
config: Legend::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LegendDemo {
|
||||
fn line_with_slope(slope: f64) -> Line {
|
||||
Line::new(Values::from_explicit_callback(move |x| slope * x, .., 100))
|
||||
|
@ -269,9 +261,10 @@ impl Widget for &mut LegendDemo {
|
|||
egui::Grid::new("settings").show(ui, |ui| {
|
||||
ui.label("Text style:");
|
||||
ui.horizontal(|ui| {
|
||||
TextStyle::all().for_each(|style| {
|
||||
ui.selectable_value(&mut config.text_style, style, format!("{:?}", style));
|
||||
});
|
||||
let all_text_styles = ui.style().text_styles();
|
||||
for style in all_text_styles {
|
||||
ui.selectable_value(&mut config.text_style, style.clone(), style.to_string());
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
|
@ -292,7 +285,9 @@ impl Widget for &mut LegendDemo {
|
|||
ui.end_row();
|
||||
});
|
||||
|
||||
let legend_plot = Plot::new("legend_demo").legend(*config).data_aspect(1.0);
|
||||
let legend_plot = Plot::new("legend_demo")
|
||||
.legend(config.clone())
|
||||
.data_aspect(1.0);
|
||||
legend_plot
|
||||
.show(ui, |plot_ui| {
|
||||
plot_ui.line(LegendDemo::line_with_slope(0.5).name("lines"));
|
||||
|
@ -305,10 +300,82 @@ impl Widget for &mut LegendDemo {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Default)]
|
||||
struct ItemsDemo {}
|
||||
#[derive(PartialEq)]
|
||||
struct LinkedAxisDemo {
|
||||
link_x: bool,
|
||||
link_y: bool,
|
||||
group: plot::LinkedAxisGroup,
|
||||
}
|
||||
|
||||
impl ItemsDemo {}
|
||||
impl Default for LinkedAxisDemo {
|
||||
fn default() -> Self {
|
||||
let link_x = true;
|
||||
let link_y = false;
|
||||
Self {
|
||||
link_x,
|
||||
link_y,
|
||||
group: plot::LinkedAxisGroup::new(link_x, link_y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LinkedAxisDemo {
|
||||
fn line_with_slope(slope: f64) -> Line {
|
||||
Line::new(Values::from_explicit_callback(move |x| slope * x, .., 100))
|
||||
}
|
||||
fn sin() -> Line {
|
||||
Line::new(Values::from_explicit_callback(move |x| x.sin(), .., 100))
|
||||
}
|
||||
fn cos() -> Line {
|
||||
Line::new(Values::from_explicit_callback(move |x| x.cos(), .., 100))
|
||||
}
|
||||
|
||||
fn configure_plot(plot_ui: &mut plot::PlotUi) {
|
||||
plot_ui.line(LinkedAxisDemo::line_with_slope(0.5));
|
||||
plot_ui.line(LinkedAxisDemo::line_with_slope(1.0));
|
||||
plot_ui.line(LinkedAxisDemo::line_with_slope(2.0));
|
||||
plot_ui.line(LinkedAxisDemo::sin());
|
||||
plot_ui.line(LinkedAxisDemo::cos());
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &mut LinkedAxisDemo {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Linked axes:");
|
||||
ui.checkbox(&mut self.link_x, "X");
|
||||
ui.checkbox(&mut self.link_y, "Y");
|
||||
});
|
||||
self.group.set_link_x(self.link_x);
|
||||
self.group.set_link_y(self.link_y);
|
||||
ui.horizontal(|ui| {
|
||||
Plot::new("linked_axis_1")
|
||||
.data_aspect(1.0)
|
||||
.width(250.0)
|
||||
.height(250.0)
|
||||
.link_axis(self.group.clone())
|
||||
.show(ui, LinkedAxisDemo::configure_plot);
|
||||
Plot::new("linked_axis_2")
|
||||
.data_aspect(2.0)
|
||||
.width(150.0)
|
||||
.height(250.0)
|
||||
.link_axis(self.group.clone())
|
||||
.show(ui, LinkedAxisDemo::configure_plot);
|
||||
});
|
||||
Plot::new("linked_axis_3")
|
||||
.data_aspect(0.5)
|
||||
.width(250.0)
|
||||
.height(150.0)
|
||||
.link_axis(self.group.clone())
|
||||
.show(ui, LinkedAxisDemo::configure_plot)
|
||||
.response
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Default)]
|
||||
struct ItemsDemo {
|
||||
texture: Option<egui::TextureHandle>,
|
||||
}
|
||||
|
||||
impl Widget for &mut ItemsDemo {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
|
@ -343,13 +410,15 @@ impl Widget for &mut ItemsDemo {
|
|||
);
|
||||
Arrows::new(arrow_origins, arrow_tips)
|
||||
};
|
||||
|
||||
let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
|
||||
ui.ctx()
|
||||
.load_texture("plot_demo", egui::ColorImage::example())
|
||||
});
|
||||
let image = PlotImage::new(
|
||||
TextureId::Egui,
|
||||
texture,
|
||||
Value::new(0.0, 10.0),
|
||||
[
|
||||
ui.fonts().font_image().width as f32 / 100.0,
|
||||
ui.fonts().font_image().height as f32 / 100.0,
|
||||
],
|
||||
5.0 * vec2(texture.aspect_ratio(), 1.0),
|
||||
);
|
||||
|
||||
let plot = Plot::new("items_demo")
|
||||
|
@ -376,15 +445,9 @@ impl Widget for &mut ItemsDemo {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Default, PartialEq)]
|
||||
struct InteractionDemo {}
|
||||
|
||||
impl Default for InteractionDemo {
|
||||
fn default() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &mut InteractionDemo {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let plot = Plot::new("interaction_demo").height(300.0);
|
||||
|
@ -532,15 +595,40 @@ impl ChartsDemo {
|
|||
.name("Set 4")
|
||||
.stack_on(&[&chart1, &chart2, &chart3]);
|
||||
|
||||
let mut x_fmt: fn(f64) -> String = |val| {
|
||||
if val >= 0.0 && val <= 4.0 && is_approx_integer(val) {
|
||||
// Only label full days from 0 to 4
|
||||
format!("Day {}", val)
|
||||
} else {
|
||||
// Otherwise return empty string (i.e. no label)
|
||||
String::new()
|
||||
}
|
||||
};
|
||||
|
||||
let mut y_fmt: fn(f64) -> String = |val| {
|
||||
let percent = 100.0 * val;
|
||||
|
||||
if is_approx_integer(percent) && !is_approx_zero(percent) {
|
||||
// Only show integer percentages,
|
||||
// and don't show at Y=0 (label overlaps with X axis label)
|
||||
format!("{}%", percent)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
};
|
||||
|
||||
if !self.vertical {
|
||||
chart1 = chart1.horizontal();
|
||||
chart2 = chart2.horizontal();
|
||||
chart3 = chart3.horizontal();
|
||||
chart4 = chart4.horizontal();
|
||||
std::mem::swap(&mut x_fmt, &mut y_fmt);
|
||||
}
|
||||
|
||||
Plot::new("Stacked Bar Chart Demo")
|
||||
.legend(Legend::default())
|
||||
.x_axis_formatter(x_fmt)
|
||||
.y_axis_formatter(y_fmt)
|
||||
.data_aspect(1.0)
|
||||
.show(ui, |plot_ui| {
|
||||
plot_ui.bar_chart(chart1);
|
||||
|
@ -623,11 +711,12 @@ enum Panel {
|
|||
Charts,
|
||||
Items,
|
||||
Interaction,
|
||||
LinkedAxes,
|
||||
}
|
||||
|
||||
impl Default for Panel {
|
||||
fn default() -> Self {
|
||||
Self::Charts
|
||||
Self::Lines
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -639,6 +728,7 @@ pub struct PlotDemo {
|
|||
charts_demo: ChartsDemo,
|
||||
items_demo: ItemsDemo,
|
||||
interaction_demo: InteractionDemo,
|
||||
linked_axes_demo: LinkedAxisDemo,
|
||||
open_panel: Panel,
|
||||
}
|
||||
|
||||
|
@ -647,7 +737,7 @@ impl super::Demo for PlotDemo {
|
|||
"🗠 Plot"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
|
||||
fn show(&mut self, ctx: &Context, open: &mut bool) {
|
||||
use super::View as _;
|
||||
Window::new(self.name())
|
||||
.open(open)
|
||||
|
@ -663,6 +753,7 @@ impl super::View for PlotDemo {
|
|||
egui::reset_button(ui, self);
|
||||
ui.collapsing("Instructions", |ui| {
|
||||
ui.label("Pan by dragging, or scroll (+ shift = horizontal).");
|
||||
ui.label("Box zooming: Right click to zoom in and zoom out using a selection.");
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
ui.label("Zoom with ctrl / ⌘ + pointer wheel, or with pinch gesture.");
|
||||
} else if cfg!(target_os = "macos") {
|
||||
|
@ -682,6 +773,7 @@ impl super::View for PlotDemo {
|
|||
ui.selectable_value(&mut self.open_panel, Panel::Charts, "Charts");
|
||||
ui.selectable_value(&mut self.open_panel, Panel::Items, "Items");
|
||||
ui.selectable_value(&mut self.open_panel, Panel::Interaction, "Interaction");
|
||||
ui.selectable_value(&mut self.open_panel, Panel::LinkedAxes, "Linked Axes");
|
||||
});
|
||||
ui.separator();
|
||||
|
||||
|
@ -704,6 +796,17 @@ impl super::View for PlotDemo {
|
|||
Panel::Interaction => {
|
||||
ui.add(&mut self.interaction_demo);
|
||||
}
|
||||
Panel::LinkedAxes => {
|
||||
ui.add(&mut self.linked_axes_demo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_approx_zero(val: f64) -> bool {
|
||||
val.abs() < 1e-6
|
||||
}
|
||||
|
||||
fn is_approx_integer(val: f64) -> bool {
|
||||
val.fract().abs() < 1e-6
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue