Merge branch 'master' into web_handle
This commit is contained in:
commit
d3e26cd00a
59 changed files with 1727 additions and 1094 deletions
25
.github/workflows/rust.yml
vendored
25
.github/workflows/rust.yml
vendored
|
@ -58,8 +58,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.61.0
|
toolchain: 1.61.0
|
||||||
|
target: wasm32-unknown-unknown
|
||||||
override: true
|
override: true
|
||||||
- run: rustup target add wasm32-unknown-unknown
|
|
||||||
- uses: actions-rs/cargo@v1
|
- uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: check
|
command: check
|
||||||
|
@ -87,8 +87,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.61.0
|
toolchain: 1.61.0
|
||||||
|
target: wasm32-unknown-unknown
|
||||||
override: true
|
override: true
|
||||||
- run: rustup target add wasm32-unknown-unknown
|
|
||||||
- name: check
|
- name: check
|
||||||
run: cargo check -p eframe --lib --no-default-features --features glow,persistence --target wasm32-unknown-unknown
|
run: cargo check -p eframe --lib --no-default-features --features glow,persistence --target wasm32-unknown-unknown
|
||||||
|
|
||||||
|
@ -101,8 +101,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.61.0
|
toolchain: 1.61.0
|
||||||
|
target: wasm32-unknown-unknown
|
||||||
override: true
|
override: true
|
||||||
- run: rustup target add wasm32-unknown-unknown
|
|
||||||
- uses: actions-rs/cargo@v1
|
- uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: check
|
command: check
|
||||||
|
@ -136,7 +136,7 @@ jobs:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.61.0
|
toolchain: 1.61.0
|
||||||
override: true
|
override: true
|
||||||
- run: rustup component add rustfmt
|
components: rustfmt
|
||||||
- uses: actions-rs/cargo@v1
|
- uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: fmt
|
command: fmt
|
||||||
|
@ -191,7 +191,22 @@ jobs:
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.61.0
|
toolchain: 1.61.0
|
||||||
|
target: wasm32-unknown-unknown
|
||||||
override: true
|
override: true
|
||||||
- run: rustup target add wasm32-unknown-unknown
|
|
||||||
- run: ./sh/setup_web.sh
|
- run: ./sh/setup_web.sh
|
||||||
- run: ./sh/wasm_bindgen_check.sh
|
- run: ./sh/wasm_bindgen_check.sh
|
||||||
|
|
||||||
|
android:
|
||||||
|
name: android
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: 1.61.0
|
||||||
|
target: aarch64-linux-android
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- run: cargo check --features wgpu --target aarch64-linux-android
|
||||||
|
working-directory: eframe
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
||||||
/.*.json
|
/.*.json
|
||||||
/.vscode
|
/.vscode
|
||||||
/media/*
|
/media/*
|
||||||
|
.DS_Store
|
||||||
|
|
|
@ -18,6 +18,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui-w
|
||||||
* Added `Contex::request_repaint_after` ([#1694](https://github.com/emilk/egui/pull/1694)).
|
* Added `Contex::request_repaint_after` ([#1694](https://github.com/emilk/egui/pull/1694)).
|
||||||
* `ctrl-h` now acts like backspace in `TextEdit` ([#1812](https://github.com/emilk/egui/pull/1812)).
|
* `ctrl-h` now acts like backspace in `TextEdit` ([#1812](https://github.com/emilk/egui/pull/1812)).
|
||||||
* Added `RawInput::has_focus` which backends can set to indicate whether the UI as a whole has the keyboard focus ([#1859](https://github.com/emilk/egui/pull/1859)).
|
* Added `RawInput::has_focus` which backends can set to indicate whether the UI as a whole has the keyboard focus ([#1859](https://github.com/emilk/egui/pull/1859)).
|
||||||
|
* Added `PointerState::button_double_clicked()` and `PointerState::button_triple_clicked()` ([#1906](https://github.com/emilk/egui/issues/1906)).
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
|
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
|
||||||
|
|
1185
Cargo.lock
generated
1185
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
14
Cargo.toml
14
Cargo.toml
|
@ -12,19 +12,7 @@ members = [
|
||||||
"emath",
|
"emath",
|
||||||
"epaint",
|
"epaint",
|
||||||
|
|
||||||
"examples/confirm_exit",
|
"examples/*",
|
||||||
"examples/custom_3d_glow",
|
|
||||||
"examples/custom_3d_three-d",
|
|
||||||
"examples/custom_font",
|
|
||||||
"examples/custom_font_style",
|
|
||||||
"examples/custom_window_frame",
|
|
||||||
"examples/download_image",
|
|
||||||
"examples/file_dialog",
|
|
||||||
"examples/hello_world",
|
|
||||||
"examples/puffin_profiler",
|
|
||||||
"examples/retained_image",
|
|
||||||
"examples/screenshot",
|
|
||||||
"examples/svg",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|
|
@ -58,6 +58,7 @@ allow = [
|
||||||
"MIT", # https://tldrlegal.com/license/mit-license
|
"MIT", # https://tldrlegal.com/license/mit-license
|
||||||
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11
|
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11
|
||||||
"OpenSSL", # https://www.openssl.org/source/license.html
|
"OpenSSL", # https://www.openssl.org/source/license.html
|
||||||
|
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
|
||||||
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
|
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ document-features = { version = "0.2", optional = true }
|
||||||
egui_glow = { version = "0.18.0", path = "../egui_glow", optional = true, default-features = false }
|
egui_glow = { version = "0.18.0", path = "../egui_glow", optional = true, default-features = false }
|
||||||
egui-wgpu = { version = "0.18.0", path = "../egui-wgpu", optional = true, features = ["winit"] }
|
egui-wgpu = { version = "0.18.0", path = "../egui-wgpu", optional = true, features = ["winit"] }
|
||||||
glow = { version = "0.11", optional = true }
|
glow = { version = "0.11", optional = true }
|
||||||
ron = { version = "0.7", optional = true }
|
ron = { version = "0.8", optional = true, features = ["integer128"] }
|
||||||
serde = { version = "1", optional = true, features = ["derive"] }
|
serde = { version = "1", optional = true, features = ["derive"] }
|
||||||
wgpu = { version = "0.13", optional = true }
|
wgpu = { version = "0.13", optional = true }
|
||||||
|
|
||||||
|
@ -83,8 +83,8 @@ wgpu = { version = "0.13", optional = true }
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
dark-light = { version = "0.2.1", optional = true }
|
dark-light = { version = "0.2.1", optional = true }
|
||||||
egui-winit = { version = "0.18.0", path = "../egui-winit", default-features = false, features = ["clipboard", "links"] }
|
egui-winit = { version = "0.18.0", path = "../egui-winit", default-features = false, features = ["clipboard", "links"] }
|
||||||
glutin = { version = "0.28.0" }
|
glutin = { version = "0.29.0" }
|
||||||
winit = "0.26.1"
|
winit = "0.27.2"
|
||||||
|
|
||||||
# optional native:
|
# optional native:
|
||||||
puffin = { version = "0.13", optional = true }
|
puffin = { version = "0.13", optional = true }
|
||||||
|
@ -94,6 +94,7 @@ directories-next = { version = "2", optional = true }
|
||||||
# web:
|
# web:
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
bytemuck = "1.7"
|
bytemuck = "1.7"
|
||||||
|
getrandom = { version = "0.2", features = ["js"] } # used by ahash
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
|
|
|
@ -30,13 +30,18 @@ pub struct CreationContext<'s> {
|
||||||
|
|
||||||
/// The [`glow::Context`] allows you to initialize OpenGL resources (e.g. shaders) that
|
/// The [`glow::Context`] allows you to initialize OpenGL resources (e.g. shaders) that
|
||||||
/// you might want to use later from a [`egui::PaintCallback`].
|
/// you might want to use later from a [`egui::PaintCallback`].
|
||||||
|
///
|
||||||
|
/// Only available when compiling with the `glow` feature and using [`Renderer::Glow`].
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
pub gl: Option<std::sync::Arc<glow::Context>>,
|
pub gl: Option<std::sync::Arc<glow::Context>>,
|
||||||
|
|
||||||
/// Can be used to manage GPU resources for custom rendering with WGPU using
|
/// The underlying WGPU render state.
|
||||||
/// [`egui::PaintCallback`]s.
|
///
|
||||||
|
/// Only available when compiling with the `wgpu` feature and using [`Renderer::Wgpu`].
|
||||||
|
///
|
||||||
|
/// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s.
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
pub render_state: Option<egui_wgpu::RenderState>,
|
pub wgpu_render_state: Option<egui_wgpu::RenderState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -281,6 +286,20 @@ pub struct NativeOptions {
|
||||||
///
|
///
|
||||||
/// Default: `Theme::Dark`.
|
/// Default: `Theme::Dark`.
|
||||||
pub default_theme: Theme,
|
pub default_theme: Theme,
|
||||||
|
|
||||||
|
/// This controls what happens when you close the main eframe window.
|
||||||
|
///
|
||||||
|
/// If `true`, execution will continue after the eframe window is closed.
|
||||||
|
/// If `false`, the app will close once the eframe window is closed.
|
||||||
|
///
|
||||||
|
/// This is `true` by default, and the `false` option is only there
|
||||||
|
/// so we can revert if we find any bugs.
|
||||||
|
///
|
||||||
|
/// This feature was introduced in <https://github.com/emilk/egui/pull/1889>.
|
||||||
|
///
|
||||||
|
/// When `true`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`] is used.
|
||||||
|
/// When `false`, [`winit::event_loop::EventLoop::run`] is used.
|
||||||
|
pub run_and_return: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
@ -307,6 +326,7 @@ impl Default for NativeOptions {
|
||||||
renderer: Renderer::default(),
|
renderer: Renderer::default(),
|
||||||
follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"),
|
follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"),
|
||||||
default_theme: Theme::Dark,
|
default_theme: Theme::Dark,
|
||||||
|
run_and_return: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -507,10 +527,9 @@ pub struct Frame {
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
pub(crate) gl: Option<std::sync::Arc<glow::Context>>,
|
pub(crate) gl: Option<std::sync::Arc<glow::Context>>,
|
||||||
|
|
||||||
/// Can be used to manage GPU resources for custom rendering with WGPU using
|
/// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s.
|
||||||
/// [`egui::PaintCallback`]s.
|
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
pub render_state: Option<egui_wgpu::RenderState>,
|
pub(crate) wgpu_render_state: Option<egui_wgpu::RenderState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Frame {
|
impl Frame {
|
||||||
|
@ -548,12 +567,22 @@ impl Frame {
|
||||||
/// ([`egui`] only collects [`egui::Shape`]s and then eframe paints them all in one go later on).
|
/// ([`egui`] only collects [`egui::Shape`]s and then eframe paints them all in one go later on).
|
||||||
///
|
///
|
||||||
/// To get a [`glow`] context you need to compile with the `glow` feature flag,
|
/// To get a [`glow`] context you need to compile with the `glow` feature flag,
|
||||||
/// and run eframe with the glow backend.
|
/// and run eframe using [`Renderer::Glow`].
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
pub fn gl(&self) -> Option<&std::sync::Arc<glow::Context>> {
|
pub fn gl(&self) -> Option<&std::sync::Arc<glow::Context>> {
|
||||||
self.gl.as_ref()
|
self.gl.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The underlying WGPU render state.
|
||||||
|
///
|
||||||
|
/// Only available when compiling with the `wgpu` feature and using [`Renderer::Wgpu`].
|
||||||
|
///
|
||||||
|
/// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s.
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
pub fn wgpu_render_state(&self) -> Option<&egui_wgpu::RenderState> {
|
||||||
|
self.wgpu_render_state.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
/// Signal the app to stop/exit/quit the app (only works for native apps, not web apps).
|
/// Signal the app to stop/exit/quit the app (only works for native apps, not web apps).
|
||||||
/// The framework will not quit immediately, but at the end of the this frame.
|
/// The framework will not quit immediately, but at the end of the this frame.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
@ -763,7 +792,10 @@ pub fn get_value<T: serde::de::DeserializeOwned>(storage: &dyn Storage, key: &st
|
||||||
/// Serialize the given value as [RON](https://github.com/ron-rs/ron) and store with the given key.
|
/// Serialize the given value as [RON](https://github.com/ron-rs/ron) and store with the given key.
|
||||||
#[cfg(feature = "ron")]
|
#[cfg(feature = "ron")]
|
||||||
pub fn set_value<T: serde::Serialize>(storage: &mut dyn Storage, key: &str, value: &T) {
|
pub fn set_value<T: serde::Serialize>(storage: &mut dyn Storage, key: &str, value: &T) {
|
||||||
storage.set_string(key, ron::ser::to_string(value).unwrap());
|
match ron::ser::to_string(value) {
|
||||||
|
Ok(string) => storage.set_string(key, string),
|
||||||
|
Err(err) => tracing::error!("eframe failed to encode data using ron: {}", err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`Storage`] key used for app
|
/// [`Storage`] key used for app
|
||||||
|
|
|
@ -161,20 +161,20 @@ mod native;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) -> ! {
|
pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) {
|
||||||
let renderer = native_options.renderer;
|
let renderer = native_options.renderer;
|
||||||
|
|
||||||
match renderer {
|
match renderer {
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
Renderer::Glow => {
|
Renderer::Glow => {
|
||||||
tracing::debug!("Using the glow renderer");
|
tracing::debug!("Using the glow renderer");
|
||||||
native::run::run_glow(app_name, &native_options, app_creator)
|
native::run::run_glow(app_name, &native_options, app_creator);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
Renderer::Wgpu => {
|
Renderer::Wgpu => {
|
||||||
tracing::debug!("Using the wgpu renderer");
|
tracing::debug!("Using the wgpu renderer");
|
||||||
native::run::run_wgpu(app_name, &native_options, app_creator)
|
native::run::run_wgpu(app_name, &native_options, app_creator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,7 +196,7 @@ impl EpiIntegration {
|
||||||
system_theme: Option<Theme>,
|
system_theme: Option<Theme>,
|
||||||
storage: Option<Box<dyn epi::Storage>>,
|
storage: Option<Box<dyn epi::Storage>>,
|
||||||
#[cfg(feature = "glow")] gl: Option<std::sync::Arc<glow::Context>>,
|
#[cfg(feature = "glow")] gl: Option<std::sync::Arc<glow::Context>>,
|
||||||
#[cfg(feature = "wgpu")] render_state: Option<egui_wgpu::RenderState>,
|
#[cfg(feature = "wgpu")] wgpu_render_state: Option<egui_wgpu::RenderState>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let egui_ctx = egui::Context::default();
|
let egui_ctx = egui::Context::default();
|
||||||
|
|
||||||
|
@ -214,7 +214,7 @@ impl EpiIntegration {
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
gl,
|
gl,
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
render_state,
|
wgpu_render_state,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut egui_winit = egui_winit::State::new(event_loop);
|
let mut egui_winit = egui_winit::State::new(event_loop);
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
use super::epi_integration;
|
//! Note that this file contains two similar paths - one for [`glow`], one for [`wgpu`].
|
||||||
use crate::epi;
|
//! When making changes to one you often also want to apply it to the other.
|
||||||
use egui_winit::winit;
|
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use egui_winit::winit;
|
||||||
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
|
|
||||||
|
use super::epi_integration::{self, EpiIntegration};
|
||||||
|
use crate::epi;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct RequestRepaintEvent;
|
struct RequestRepaintEvent;
|
||||||
|
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
|
@ -9,7 +18,7 @@ struct RequestRepaintEvent;
|
||||||
fn create_display(
|
fn create_display(
|
||||||
native_options: &NativeOptions,
|
native_options: &NativeOptions,
|
||||||
window_builder: winit::window::WindowBuilder,
|
window_builder: winit::window::WindowBuilder,
|
||||||
event_loop: &winit::event_loop::EventLoop<RequestRepaintEvent>,
|
event_loop: &EventLoop<RequestRepaintEvent>,
|
||||||
) -> (
|
) -> (
|
||||||
glutin::WindowedContext<glutin::PossiblyCurrent>,
|
glutin::WindowedContext<glutin::PossiblyCurrent>,
|
||||||
glow::Context,
|
glow::Context,
|
||||||
|
@ -47,28 +56,210 @@ fn create_display(
|
||||||
|
|
||||||
pub use epi::NativeOptions;
|
pub use epi::NativeOptions;
|
||||||
|
|
||||||
|
enum EventResult {
|
||||||
|
Wait,
|
||||||
|
RepaintAsap,
|
||||||
|
RepaintAt(Instant),
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait WinitApp {
|
||||||
|
fn is_focused(&self) -> bool;
|
||||||
|
fn integration(&self) -> &EpiIntegration;
|
||||||
|
fn window(&self) -> &winit::window::Window;
|
||||||
|
fn save_and_destroy(&mut self);
|
||||||
|
fn paint(&mut self) -> EventResult;
|
||||||
|
fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access a thread-local event loop.
|
||||||
|
///
|
||||||
|
/// We reuse the event-loop so we can support closing and opening an eframe window
|
||||||
|
/// multiple times. This is just a limitation of winit.
|
||||||
|
fn with_event_loop(f: impl FnOnce(&mut EventLoop<RequestRepaintEvent>)) {
|
||||||
|
use std::cell::RefCell;
|
||||||
|
thread_local!(static EVENT_LOOP: RefCell<EventLoop<RequestRepaintEvent>> = RefCell::new(winit::event_loop::EventLoopBuilder::with_user_event().build()));
|
||||||
|
|
||||||
|
EVENT_LOOP.with(|event_loop| {
|
||||||
|
f(&mut *event_loop.borrow_mut());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_and_return(event_loop: &mut EventLoop<RequestRepaintEvent>, mut winit_app: impl WinitApp) {
|
||||||
|
use winit::platform::run_return::EventLoopExtRunReturn as _;
|
||||||
|
|
||||||
|
tracing::debug!("event_loop.run_return");
|
||||||
|
|
||||||
|
let mut next_repaint_time = Instant::now();
|
||||||
|
|
||||||
|
event_loop.run_return(|event, _, control_flow| {
|
||||||
|
let event_result = match event {
|
||||||
|
winit::event::Event::LoopDestroyed => EventResult::Exit,
|
||||||
|
|
||||||
|
// Platform-dependent event handlers to workaround a winit bug
|
||||||
|
// See: https://github.com/rust-windowing/winit/issues/987
|
||||||
|
// See: https://github.com/rust-windowing/winit/issues/1619
|
||||||
|
winit::event::Event::RedrawEventsCleared if cfg!(windows) => {
|
||||||
|
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||||
|
winit_app.paint()
|
||||||
|
}
|
||||||
|
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => {
|
||||||
|
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||||
|
winit_app.paint()
|
||||||
|
}
|
||||||
|
|
||||||
|
winit::event::Event::UserEvent(RequestRepaintEvent)
|
||||||
|
| winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
||||||
|
..
|
||||||
|
}) => EventResult::RepaintAsap,
|
||||||
|
|
||||||
|
winit::event::Event::WindowEvent { window_id, .. }
|
||||||
|
if window_id != winit_app.window().id() =>
|
||||||
|
{
|
||||||
|
// This can happen if we close a window, and then reopen a new one,
|
||||||
|
// or if we have multiple windows open.
|
||||||
|
EventResult::Wait
|
||||||
|
}
|
||||||
|
|
||||||
|
event => winit_app.on_event(event),
|
||||||
|
};
|
||||||
|
|
||||||
|
match event_result {
|
||||||
|
EventResult::Wait => {}
|
||||||
|
EventResult::RepaintAsap => {
|
||||||
|
next_repaint_time = Instant::now();
|
||||||
|
}
|
||||||
|
EventResult::RepaintAt(repaint_time) => {
|
||||||
|
next_repaint_time = next_repaint_time.min(repaint_time);
|
||||||
|
}
|
||||||
|
EventResult::Exit => {
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*control_flow = match next_repaint_time.checked_duration_since(Instant::now()) {
|
||||||
|
None => {
|
||||||
|
winit_app.window().request_redraw();
|
||||||
|
ControlFlow::Poll
|
||||||
|
}
|
||||||
|
Some(time_until_next_repaint) => {
|
||||||
|
ControlFlow::WaitUntil(Instant::now() + time_until_next_repaint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tracing::debug!("eframe window closed");
|
||||||
|
|
||||||
|
winit_app.save_and_destroy();
|
||||||
|
|
||||||
|
drop(winit_app);
|
||||||
|
|
||||||
|
// Needed to clean the event_loop:
|
||||||
|
event_loop.run_return(|_, _, control_flow| {
|
||||||
|
control_flow.set_exit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_and_exit(
|
||||||
|
event_loop: EventLoop<RequestRepaintEvent>,
|
||||||
|
mut winit_app: impl WinitApp + 'static,
|
||||||
|
) -> ! {
|
||||||
|
tracing::debug!("event_loop.run");
|
||||||
|
|
||||||
|
let mut next_repaint_time = Instant::now();
|
||||||
|
|
||||||
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
let event_result = match event {
|
||||||
|
winit::event::Event::LoopDestroyed => EventResult::Exit,
|
||||||
|
|
||||||
|
// Platform-dependent event handlers to workaround a winit bug
|
||||||
|
// See: https://github.com/rust-windowing/winit/issues/987
|
||||||
|
// See: https://github.com/rust-windowing/winit/issues/1619
|
||||||
|
winit::event::Event::RedrawEventsCleared if cfg!(windows) => {
|
||||||
|
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||||
|
winit_app.paint()
|
||||||
|
}
|
||||||
|
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => {
|
||||||
|
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||||
|
winit_app.paint()
|
||||||
|
}
|
||||||
|
|
||||||
|
winit::event::Event::UserEvent(RequestRepaintEvent)
|
||||||
|
| winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
||||||
|
..
|
||||||
|
}) => EventResult::RepaintAsap,
|
||||||
|
|
||||||
|
event => winit_app.on_event(event),
|
||||||
|
};
|
||||||
|
|
||||||
|
match event_result {
|
||||||
|
EventResult::Wait => {}
|
||||||
|
EventResult::RepaintAsap => {
|
||||||
|
next_repaint_time = Instant::now();
|
||||||
|
}
|
||||||
|
EventResult::RepaintAt(repaint_time) => {
|
||||||
|
next_repaint_time = next_repaint_time.min(repaint_time);
|
||||||
|
}
|
||||||
|
EventResult::Exit => {
|
||||||
|
tracing::debug!("Quitting…");
|
||||||
|
winit_app.save_and_destroy();
|
||||||
|
#[allow(clippy::exit)]
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*control_flow = match next_repaint_time.checked_duration_since(Instant::now()) {
|
||||||
|
None => {
|
||||||
|
winit_app.window().request_redraw();
|
||||||
|
ControlFlow::Poll
|
||||||
|
}
|
||||||
|
Some(time_until_next_repaint) => {
|
||||||
|
ControlFlow::WaitUntil(Instant::now() + time_until_next_repaint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Run an egui app
|
/// Run an egui app
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
pub fn run_glow(
|
mod glow_integration {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct GlowWinitApp {
|
||||||
|
gl_window: glutin::WindowedContext<glutin::PossiblyCurrent>,
|
||||||
|
gl: Arc<glow::Context>,
|
||||||
|
painter: egui_glow::Painter,
|
||||||
|
integration: epi_integration::EpiIntegration,
|
||||||
|
app: Box<dyn epi::App>,
|
||||||
|
is_focused: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlowWinitApp {
|
||||||
|
fn new(
|
||||||
|
event_loop: &EventLoop<RequestRepaintEvent>,
|
||||||
app_name: &str,
|
app_name: &str,
|
||||||
native_options: &epi::NativeOptions,
|
native_options: &epi::NativeOptions,
|
||||||
app_creator: epi::AppCreator,
|
app_creator: epi::AppCreator,
|
||||||
) -> ! {
|
) -> Self {
|
||||||
let storage = epi_integration::create_storage(app_name);
|
let storage = epi_integration::create_storage(app_name);
|
||||||
let window_settings = epi_integration::load_window_settings(storage.as_deref());
|
let window_settings = epi_integration::load_window_settings(storage.as_deref());
|
||||||
let event_loop = winit::event_loop::EventLoop::with_user_event();
|
|
||||||
|
|
||||||
let window_builder =
|
let window_builder = epi_integration::window_builder(native_options, &window_settings)
|
||||||
epi_integration::window_builder(native_options, &window_settings).with_title(app_name);
|
.with_title(app_name);
|
||||||
let (gl_window, gl) = create_display(native_options, window_builder, &event_loop);
|
let (gl_window, gl) = create_display(native_options, window_builder, event_loop);
|
||||||
let gl = std::sync::Arc::new(gl);
|
let gl = Arc::new(gl);
|
||||||
|
|
||||||
let mut painter = egui_glow::Painter::new(gl.clone(), None, "")
|
let painter = egui_glow::Painter::new(gl.clone(), None, "")
|
||||||
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
|
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
|
||||||
|
|
||||||
let system_theme = native_options.system_theme();
|
let system_theme = native_options.system_theme();
|
||||||
let mut integration = epi_integration::EpiIntegration::new(
|
let mut integration = epi_integration::EpiIntegration::new(
|
||||||
&event_loop,
|
event_loop,
|
||||||
painter.max_texture_side(),
|
painter.max_texture_side(),
|
||||||
gl_window.window(),
|
gl_window.window(),
|
||||||
system_theme,
|
system_theme,
|
||||||
|
@ -93,27 +284,63 @@ pub fn run_glow(
|
||||||
storage: integration.frame.storage(),
|
storage: integration.frame.storage(),
|
||||||
gl: Some(gl.clone()),
|
gl: Some(gl.clone()),
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
render_state: None,
|
wgpu_render_state: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
if app.warm_up_enabled() {
|
if app.warm_up_enabled() {
|
||||||
integration.warm_up(app.as_mut(), gl_window.window());
|
integration.warm_up(app.as_mut(), gl_window.window());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut is_focused = true;
|
Self {
|
||||||
|
gl_window,
|
||||||
|
gl,
|
||||||
|
painter,
|
||||||
|
integration,
|
||||||
|
app,
|
||||||
|
is_focused: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
impl WinitApp for GlowWinitApp {
|
||||||
let window = gl_window.window();
|
fn is_focused(&self) -> bool {
|
||||||
|
self.is_focused
|
||||||
|
}
|
||||||
|
|
||||||
let mut redraw = || {
|
fn integration(&self) -> &EpiIntegration {
|
||||||
|
&self.integration
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> &winit::window::Window {
|
||||||
|
self.gl_window.window()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_and_destroy(&mut self) {
|
||||||
|
self.integration
|
||||||
|
.save(&mut *self.app, self.gl_window.window());
|
||||||
|
self.app.on_exit(Some(&self.gl));
|
||||||
|
self.painter.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) -> EventResult {
|
||||||
#[cfg(feature = "puffin")]
|
#[cfg(feature = "puffin")]
|
||||||
puffin::GlobalProfiler::lock().new_frame();
|
puffin::GlobalProfiler::lock().new_frame();
|
||||||
crate::profile_scope!("frame");
|
crate::profile_scope!("frame");
|
||||||
|
|
||||||
|
let Self {
|
||||||
|
gl_window,
|
||||||
|
gl,
|
||||||
|
app,
|
||||||
|
integration,
|
||||||
|
painter,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
let window = gl_window.window();
|
||||||
|
|
||||||
let screen_size_in_pixels: [u32; 2] = window.inner_size().into();
|
let screen_size_in_pixels: [u32; 2] = window.inner_size().into();
|
||||||
|
|
||||||
egui_glow::painter::clear(
|
egui_glow::painter::clear(
|
||||||
&gl,
|
gl,
|
||||||
screen_size_in_pixels,
|
screen_size_in_pixels,
|
||||||
app.clear_color(&integration.egui_ctx.style().visuals),
|
app.clear_color(&integration.egui_ctx.style().visuals),
|
||||||
);
|
);
|
||||||
|
@ -146,11 +373,10 @@ pub fn run_glow(
|
||||||
gl_window.swap_buffers().unwrap();
|
gl_window.swap_buffers().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
*control_flow = if integration.should_quit() {
|
let control_flow = if integration.should_quit() {
|
||||||
winit::event_loop::ControlFlow::Exit
|
EventResult::Exit
|
||||||
} else if repaint_after.is_zero() {
|
} else if repaint_after.is_zero() {
|
||||||
window.request_redraw();
|
EventResult::RepaintAsap
|
||||||
winit::event_loop::ControlFlow::Poll
|
|
||||||
} else if let Some(repaint_after_instant) =
|
} else if let Some(repaint_after_instant) =
|
||||||
std::time::Instant::now().checked_add(repaint_after)
|
std::time::Instant::now().checked_add(repaint_after)
|
||||||
{
|
{
|
||||||
|
@ -159,14 +385,14 @@ pub fn run_glow(
|
||||||
// technically, this might lead to some weird corner cases where the user *WANTS*
|
// technically, this might lead to some weird corner cases where the user *WANTS*
|
||||||
// winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own
|
// winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own
|
||||||
// egui backend impl i guess.
|
// egui backend impl i guess.
|
||||||
winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant)
|
EventResult::RepaintAt(repaint_after_instant)
|
||||||
} else {
|
} else {
|
||||||
winit::event_loop::ControlFlow::Wait
|
EventResult::Wait
|
||||||
};
|
};
|
||||||
|
|
||||||
integration.maybe_autosave(app.as_mut(), window);
|
integration.maybe_autosave(app.as_mut(), window);
|
||||||
|
|
||||||
if !is_focused {
|
if !self.is_focused {
|
||||||
// On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325
|
// On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325
|
||||||
// We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208
|
// We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208
|
||||||
// But we know if we are focused (in foreground). When minimized, we are not focused.
|
// But we know if we are focused (in foreground). When minimized, we are not focused.
|
||||||
|
@ -175,78 +401,106 @@ pub fn run_glow(
|
||||||
crate::profile_scope!("bg_sleep");
|
crate::profile_scope!("bg_sleep");
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
};
|
|
||||||
match event {
|
|
||||||
// Platform-dependent event handlers to workaround a winit bug
|
|
||||||
// See: https://github.com/rust-windowing/winit/issues/987
|
|
||||||
// See: https://github.com/rust-windowing/winit/issues/1619
|
|
||||||
winit::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(),
|
|
||||||
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(),
|
|
||||||
|
|
||||||
|
control_flow
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult {
|
||||||
|
match event {
|
||||||
winit::event::Event::WindowEvent { event, .. } => {
|
winit::event::Event::WindowEvent { event, .. } => {
|
||||||
match &event {
|
match &event {
|
||||||
winit::event::WindowEvent::Focused(new_focused) => {
|
winit::event::WindowEvent::Focused(new_focused) => {
|
||||||
is_focused = *new_focused;
|
self.is_focused = *new_focused;
|
||||||
}
|
}
|
||||||
winit::event::WindowEvent::Resized(physical_size) => {
|
winit::event::WindowEvent::Resized(physical_size) => {
|
||||||
// Resize with 0 width and height is used by winit to signal a minimize event on Windows.
|
// Resize with 0 width and height is used by winit to signal a minimize event on Windows.
|
||||||
// See: https://github.com/rust-windowing/winit/issues/208
|
// See: https://github.com/rust-windowing/winit/issues/208
|
||||||
// This solves an issue where the app would panic when minimizing on Windows.
|
// This solves an issue where the app would panic when minimizing on Windows.
|
||||||
if physical_size.width > 0 && physical_size.height > 0 {
|
if physical_size.width > 0 && physical_size.height > 0 {
|
||||||
gl_window.resize(*physical_size);
|
self.gl_window.resize(*physical_size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
winit::event::WindowEvent::ScaleFactorChanged {
|
||||||
gl_window.resize(**new_inner_size);
|
new_inner_size, ..
|
||||||
|
} => {
|
||||||
|
self.gl_window.resize(**new_inner_size);
|
||||||
}
|
}
|
||||||
winit::event::WindowEvent::CloseRequested if integration.should_quit() => {
|
winit::event::WindowEvent::CloseRequested
|
||||||
*control_flow = winit::event_loop::ControlFlow::Exit;
|
if self.integration.should_quit() =>
|
||||||
|
{
|
||||||
|
return EventResult::Exit
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
integration.on_event(app.as_mut(), &event);
|
self.integration.on_event(self.app.as_mut(), &event);
|
||||||
if integration.should_quit() {
|
|
||||||
*control_flow = winit::event_loop::ControlFlow::Exit;
|
|
||||||
}
|
|
||||||
window.request_redraw(); // TODO(emilk): ask egui if the events warrants a repaint instead
|
|
||||||
}
|
|
||||||
winit::event::Event::LoopDestroyed => {
|
|
||||||
integration.save(&mut *app, window);
|
|
||||||
app.on_exit(Some(&gl));
|
|
||||||
painter.destroy();
|
|
||||||
}
|
|
||||||
winit::event::Event::UserEvent(RequestRepaintEvent) => window.request_redraw(),
|
|
||||||
winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(emilk): merge with with the clone above
|
if self.integration.should_quit() {
|
||||||
/// Run an egui app
|
EventResult::Exit
|
||||||
#[cfg(feature = "wgpu")]
|
} else {
|
||||||
pub fn run_wgpu(
|
// TODO(emilk): ask egui if the event warrants a repaint
|
||||||
|
EventResult::RepaintAsap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => EventResult::Wait,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_glow(
|
||||||
app_name: &str,
|
app_name: &str,
|
||||||
native_options: &epi::NativeOptions,
|
native_options: &epi::NativeOptions,
|
||||||
app_creator: epi::AppCreator,
|
app_creator: epi::AppCreator,
|
||||||
) -> ! {
|
) {
|
||||||
|
if native_options.run_and_return {
|
||||||
|
with_event_loop(|event_loop| {
|
||||||
|
let glow_eframe =
|
||||||
|
GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
|
||||||
|
run_and_return(event_loop, glow_eframe);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let event_loop = winit::event_loop::EventLoopBuilder::with_user_event().build();
|
||||||
|
let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator);
|
||||||
|
run_and_exit(event_loop, glow_eframe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "glow")]
|
||||||
|
pub use glow_integration::run_glow;
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
mod wgpu_integration {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct WgpuWinitApp {
|
||||||
|
window: winit::window::Window,
|
||||||
|
painter: egui_wgpu::winit::Painter<'static>,
|
||||||
|
integration: epi_integration::EpiIntegration,
|
||||||
|
app: Box<dyn epi::App>,
|
||||||
|
is_focused: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WgpuWinitApp {
|
||||||
|
fn new(
|
||||||
|
event_loop: &EventLoop<RequestRepaintEvent>,
|
||||||
|
app_name: &str,
|
||||||
|
native_options: &epi::NativeOptions,
|
||||||
|
app_creator: epi::AppCreator,
|
||||||
|
) -> Self {
|
||||||
let storage = epi_integration::create_storage(app_name);
|
let storage = epi_integration::create_storage(app_name);
|
||||||
let window_settings = epi_integration::load_window_settings(storage.as_deref());
|
let window_settings = epi_integration::load_window_settings(storage.as_deref());
|
||||||
let event_loop = winit::event_loop::EventLoop::with_user_event();
|
|
||||||
|
|
||||||
let window = epi_integration::window_builder(native_options, &window_settings)
|
let window = epi_integration::window_builder(native_options, &window_settings)
|
||||||
.with_title(app_name)
|
.with_title(app_name)
|
||||||
.build(&event_loop)
|
.build(event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// SAFETY: `window` must outlive `painter`.
|
// SAFETY: `window` must outlive `painter`.
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code, unused_mut, unused_unsafe)]
|
||||||
let mut painter = unsafe {
|
let painter = unsafe {
|
||||||
let mut painter = egui_wgpu::winit::Painter::new(
|
let mut painter = egui_wgpu::winit::Painter::new(
|
||||||
wgpu::Backends::PRIMARY | wgpu::Backends::GL,
|
wgpu::Backends::PRIMARY | wgpu::Backends::GL,
|
||||||
wgpu::PowerPreference::HighPerformance,
|
wgpu::PowerPreference::HighPerformance,
|
||||||
|
@ -263,18 +517,18 @@ pub fn run_wgpu(
|
||||||
painter
|
painter
|
||||||
};
|
};
|
||||||
|
|
||||||
let render_state = painter.get_render_state().expect("Uninitialized");
|
let wgpu_render_state = painter.render_state();
|
||||||
|
|
||||||
let system_theme = native_options.system_theme();
|
let system_theme = native_options.system_theme();
|
||||||
let mut integration = epi_integration::EpiIntegration::new(
|
let mut integration = epi_integration::EpiIntegration::new(
|
||||||
&event_loop,
|
event_loop,
|
||||||
painter.max_texture_side().unwrap_or(2048),
|
painter.max_texture_side().unwrap_or(2048),
|
||||||
&window,
|
&window,
|
||||||
system_theme,
|
system_theme,
|
||||||
storage,
|
storage,
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
None,
|
None,
|
||||||
Some(render_state.clone()),
|
wgpu_render_state.clone(),
|
||||||
);
|
);
|
||||||
let theme = system_theme.unwrap_or(native_options.default_theme);
|
let theme = system_theme.unwrap_or(native_options.default_theme);
|
||||||
integration.egui_ctx.set_visuals(theme.egui_visuals());
|
integration.egui_ctx.set_visuals(theme.egui_visuals());
|
||||||
|
@ -292,23 +546,61 @@ pub fn run_wgpu(
|
||||||
storage: integration.frame.storage(),
|
storage: integration.frame.storage(),
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
gl: None,
|
gl: None,
|
||||||
render_state: Some(render_state),
|
wgpu_render_state,
|
||||||
});
|
});
|
||||||
|
|
||||||
if app.warm_up_enabled() {
|
if app.warm_up_enabled() {
|
||||||
integration.warm_up(app.as_mut(), &window);
|
integration.warm_up(app.as_mut(), &window);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut is_focused = true;
|
Self {
|
||||||
|
window,
|
||||||
|
painter,
|
||||||
|
integration,
|
||||||
|
app,
|
||||||
|
is_focused: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
impl WinitApp for WgpuWinitApp {
|
||||||
let window = &window;
|
fn is_focused(&self) -> bool {
|
||||||
|
self.is_focused
|
||||||
|
}
|
||||||
|
|
||||||
let mut redraw = || {
|
fn integration(&self) -> &EpiIntegration {
|
||||||
|
&self.integration
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> &winit::window::Window {
|
||||||
|
&self.window
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_and_destroy(&mut self) {
|
||||||
|
self.integration.save(&mut *self.app, &self.window);
|
||||||
|
|
||||||
|
#[cfg(feature = "glow")]
|
||||||
|
self.app.on_exit(None);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "glow"))]
|
||||||
|
self.app.on_exit();
|
||||||
|
|
||||||
|
self.painter.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) -> EventResult {
|
||||||
#[cfg(feature = "puffin")]
|
#[cfg(feature = "puffin")]
|
||||||
puffin::GlobalProfiler::lock().new_frame();
|
puffin::GlobalProfiler::lock().new_frame();
|
||||||
crate::profile_scope!("frame");
|
crate::profile_scope!("frame");
|
||||||
|
|
||||||
|
let Self {
|
||||||
|
window,
|
||||||
|
app,
|
||||||
|
integration,
|
||||||
|
painter,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
|
||||||
let egui::FullOutput {
|
let egui::FullOutput {
|
||||||
platform_output,
|
platform_output,
|
||||||
repaint_after,
|
repaint_after,
|
||||||
|
@ -330,11 +622,12 @@ pub fn run_wgpu(
|
||||||
&textures_delta,
|
&textures_delta,
|
||||||
);
|
);
|
||||||
|
|
||||||
*control_flow = if integration.should_quit() {
|
integration.post_rendering(app.as_mut(), window);
|
||||||
winit::event_loop::ControlFlow::Exit
|
|
||||||
|
let control_flow = if integration.should_quit() {
|
||||||
|
EventResult::Exit
|
||||||
} else if repaint_after.is_zero() {
|
} else if repaint_after.is_zero() {
|
||||||
window.request_redraw();
|
EventResult::RepaintAsap
|
||||||
winit::event_loop::ControlFlow::Poll
|
|
||||||
} else if let Some(repaint_after_instant) =
|
} else if let Some(repaint_after_instant) =
|
||||||
std::time::Instant::now().checked_add(repaint_after)
|
std::time::Instant::now().checked_add(repaint_after)
|
||||||
{
|
{
|
||||||
|
@ -343,14 +636,14 @@ pub fn run_wgpu(
|
||||||
// technically, this might lead to some weird corner cases where the user *WANTS*
|
// technically, this might lead to some weird corner cases where the user *WANTS*
|
||||||
// winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own
|
// winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own
|
||||||
// egui backend impl i guess.
|
// egui backend impl i guess.
|
||||||
winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant)
|
EventResult::RepaintAt(repaint_after_instant)
|
||||||
} else {
|
} else {
|
||||||
winit::event_loop::ControlFlow::Wait
|
EventResult::Wait
|
||||||
};
|
};
|
||||||
|
|
||||||
integration.maybe_autosave(app.as_mut(), window);
|
integration.maybe_autosave(app.as_mut(), window);
|
||||||
|
|
||||||
if !is_focused {
|
if !self.is_focused {
|
||||||
// On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325
|
// On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325
|
||||||
// We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208
|
// We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208
|
||||||
// But we know if we are focused (in foreground). When minimized, we are not focused.
|
// But we know if we are focused (in foreground). When minimized, we are not focused.
|
||||||
|
@ -359,70 +652,84 @@ pub fn run_wgpu(
|
||||||
crate::profile_scope!("bg_sleep");
|
crate::profile_scope!("bg_sleep");
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
control_flow
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult {
|
||||||
match event {
|
match event {
|
||||||
// Platform-dependent event handlers to workaround a winit bug
|
|
||||||
// See: https://github.com/rust-windowing/winit/issues/987
|
|
||||||
// See: https://github.com/rust-windowing/winit/issues/1619
|
|
||||||
winit::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(),
|
|
||||||
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(),
|
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
winit::event::Event::Resumed => unsafe {
|
winit::event::Event::Resumed => unsafe {
|
||||||
painter.set_window(Some(&window));
|
self.painter.set_window(Some(&self.window));
|
||||||
|
EventResult::RepaintAsap
|
||||||
},
|
},
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
winit::event::Event::Paused => unsafe {
|
winit::event::Event::Suspended => unsafe {
|
||||||
painter.set_window(None);
|
self.painter.set_window(None);
|
||||||
|
EventResult::Wait
|
||||||
},
|
},
|
||||||
|
|
||||||
winit::event::Event::WindowEvent { event, .. } => {
|
winit::event::Event::WindowEvent { event, .. } => {
|
||||||
match &event {
|
match &event {
|
||||||
winit::event::WindowEvent::Focused(new_focused) => {
|
winit::event::WindowEvent::Focused(new_focused) => {
|
||||||
is_focused = *new_focused;
|
self.is_focused = *new_focused;
|
||||||
}
|
}
|
||||||
winit::event::WindowEvent::Resized(physical_size) => {
|
winit::event::WindowEvent::Resized(physical_size) => {
|
||||||
// Resize with 0 width and height is used by winit to signal a minimize event on Windows.
|
// Resize with 0 width and height is used by winit to signal a minimize event on Windows.
|
||||||
// See: https://github.com/rust-windowing/winit/issues/208
|
// See: https://github.com/rust-windowing/winit/issues/208
|
||||||
// This solves an issue where the app would panic when minimizing on Windows.
|
// This solves an issue where the app would panic when minimizing on Windows.
|
||||||
if physical_size.width > 0 && physical_size.height > 0 {
|
if physical_size.width > 0 && physical_size.height > 0 {
|
||||||
painter.on_window_resized(physical_size.width, physical_size.height);
|
self.painter
|
||||||
|
.on_window_resized(physical_size.width, physical_size.height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
winit::event::WindowEvent::ScaleFactorChanged {
|
||||||
painter.on_window_resized(new_inner_size.width, new_inner_size.height);
|
new_inner_size, ..
|
||||||
|
} => {
|
||||||
|
self.painter
|
||||||
|
.on_window_resized(new_inner_size.width, new_inner_size.height);
|
||||||
}
|
}
|
||||||
winit::event::WindowEvent::CloseRequested if integration.should_quit() => {
|
winit::event::WindowEvent::CloseRequested
|
||||||
*control_flow = winit::event_loop::ControlFlow::Exit;
|
if self.integration.should_quit() =>
|
||||||
|
{
|
||||||
|
return EventResult::Exit
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
integration.on_event(app.as_mut(), &event);
|
self.integration.on_event(self.app.as_mut(), &event);
|
||||||
if integration.should_quit() {
|
if self.integration.should_quit() {
|
||||||
*control_flow = winit::event_loop::ControlFlow::Exit;
|
EventResult::Exit
|
||||||
|
} else {
|
||||||
|
// TODO(emilk): ask egui if the event warrants a repaint
|
||||||
|
EventResult::RepaintAsap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => EventResult::Wait,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
window.request_redraw(); // TODO(emilk): ask egui if the events warrants a repaint instead
|
|
||||||
}
|
}
|
||||||
winit::event::Event::LoopDestroyed => {
|
|
||||||
integration.save(&mut *app, window);
|
|
||||||
|
|
||||||
#[cfg(feature = "glow")]
|
pub fn run_wgpu(
|
||||||
app.on_exit(None);
|
app_name: &str,
|
||||||
|
native_options: &epi::NativeOptions,
|
||||||
#[cfg(not(feature = "glow"))]
|
app_creator: epi::AppCreator,
|
||||||
app.on_exit();
|
) {
|
||||||
|
if native_options.run_and_return {
|
||||||
painter.destroy();
|
with_event_loop(|event_loop| {
|
||||||
}
|
let wgpu_eframe =
|
||||||
winit::event::Event::UserEvent(RequestRepaintEvent) => window.request_redraw(),
|
WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
|
||||||
winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
run_and_return(event_loop, wgpu_eframe);
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
let event_loop = winit::event_loop::EventLoopBuilder::with_user_event().build();
|
||||||
|
let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator);
|
||||||
|
run_and_exit(event_loop, wgpu_eframe);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
pub use wgpu_integration::run_wgpu;
|
||||||
|
|
|
@ -219,7 +219,7 @@ impl AppRunner {
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
gl: Some(painter.painter.gl().clone()),
|
gl: Some(painter.painter.gl().clone()),
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
render_state: None,
|
wgpu_render_state: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
let frame = epi::Frame {
|
let frame = epi::Frame {
|
||||||
|
@ -229,7 +229,7 @@ impl AppRunner {
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
gl: Some(painter.gl().clone()),
|
gl: Some(painter.gl().clone()),
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
render_state: None,
|
wgpu_render_state: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
|
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
|
||||||
|
|
|
@ -47,4 +47,4 @@ wgpu = "0.13"
|
||||||
document-features = { version = "0.2", optional = true }
|
document-features = { version = "0.2", optional = true }
|
||||||
|
|
||||||
pollster = { version = "0.2", optional = true }
|
pollster = { version = "0.2", optional = true }
|
||||||
winit = { version = "0.26", optional = true }
|
winit = { version = "0.27.2", optional = true }
|
||||||
|
|
|
@ -569,7 +569,7 @@ impl RenderPass {
|
||||||
/// This could be used by custom paint hooks to render images that have been added through with
|
/// This could be used by custom paint hooks to render images that have been added through with
|
||||||
/// [`egui_extras::RetainedImage`](https://docs.rs/egui_extras/latest/egui_extras/image/struct.RetainedImage.html)
|
/// [`egui_extras::RetainedImage`](https://docs.rs/egui_extras/latest/egui_extras/image/struct.RetainedImage.html)
|
||||||
/// or [`egui::Context::load_texture`].
|
/// or [`egui::Context::load_texture`].
|
||||||
pub fn get_texture(
|
pub fn texture(
|
||||||
&self,
|
&self,
|
||||||
id: &egui::TextureId,
|
id: &egui::TextureId,
|
||||||
) -> Option<&(Option<wgpu::Texture>, wgpu::BindGroup)> {
|
) -> Option<&(Option<wgpu::Texture>, wgpu::BindGroup)> {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use egui::mutex::RwLock;
|
use egui::mutex::RwLock;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use wgpu::{Adapter, Instance, Surface, TextureFormat};
|
use wgpu::{Adapter, Instance, Surface};
|
||||||
|
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ use crate::renderer;
|
||||||
pub struct RenderState {
|
pub struct RenderState {
|
||||||
pub device: Arc<wgpu::Device>,
|
pub device: Arc<wgpu::Device>,
|
||||||
pub queue: Arc<wgpu::Queue>,
|
pub queue: Arc<wgpu::Queue>,
|
||||||
pub target_format: TextureFormat,
|
pub target_format: wgpu::TextureFormat,
|
||||||
pub egui_rpass: Arc<RwLock<renderer::RenderPass>>,
|
pub egui_rpass: Arc<RwLock<renderer::RenderPass>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,14 +75,14 @@ impl<'a> Painter<'a> {
|
||||||
/// Get the [`RenderState`].
|
/// Get the [`RenderState`].
|
||||||
///
|
///
|
||||||
/// Will return [`None`] if the render state has not been initialized yet.
|
/// Will return [`None`] if the render state has not been initialized yet.
|
||||||
pub fn get_render_state(&self) -> Option<RenderState> {
|
pub fn render_state(&self) -> Option<RenderState> {
|
||||||
self.render_state.as_ref().cloned()
|
self.render_state.as_ref().cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn init_render_state(
|
async fn init_render_state(
|
||||||
&self,
|
&self,
|
||||||
adapter: &Adapter,
|
adapter: &Adapter,
|
||||||
target_format: TextureFormat,
|
target_format: wgpu::TextureFormat,
|
||||||
) -> RenderState {
|
) -> RenderState {
|
||||||
let (device, queue) =
|
let (device, queue) =
|
||||||
pollster::block_on(adapter.request_device(&self.device_descriptor, None)).unwrap();
|
pollster::block_on(adapter.request_device(&self.device_descriptor, None)).unwrap();
|
||||||
|
|
|
@ -46,10 +46,9 @@ egui = { version = "0.18.0", path = "../egui", default-features = false, feature
|
||||||
] }
|
] }
|
||||||
instant = { version = "0.1", features = ["wasm-bindgen"] } # We use instant so we can (maybe) compile for web
|
instant = { version = "0.1", features = ["wasm-bindgen"] } # We use instant so we can (maybe) compile for web
|
||||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||||
winit = "0.26.1"
|
winit = "0.27.2"
|
||||||
|
|
||||||
#! ### Optional dependencies
|
#! ### Optional dependencies
|
||||||
arboard = { version = "2.1", optional = true, default-features = false }
|
|
||||||
|
|
||||||
## Enable this when generating docs.
|
## Enable this when generating docs.
|
||||||
document-features = { version = "0.2", optional = true }
|
document-features = { version = "0.2", optional = true }
|
||||||
|
@ -64,3 +63,6 @@ webbrowser = { version = "0.7", optional = true }
|
||||||
|
|
||||||
[target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies]
|
[target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies]
|
||||||
smithay-clipboard = { version = "0.6.3", optional = true }
|
smithay-clipboard = { version = "0.6.3", optional = true }
|
||||||
|
|
||||||
|
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||||
|
arboard = { version = "2.1", optional = true, default-features = false }
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::os::raw::c_void;
|
||||||
/// If the "clipboard" feature is off, or we cannot connect to the OS clipboard,
|
/// If the "clipboard" feature is off, or we cannot connect to the OS clipboard,
|
||||||
/// then a fallback clipboard that just works works within the same app is used instead.
|
/// then a fallback clipboard that just works works within the same app is used instead.
|
||||||
pub struct Clipboard {
|
pub struct Clipboard {
|
||||||
#[cfg(feature = "arboard")]
|
#[cfg(all(feature = "arboard", not(target_os = "android")))]
|
||||||
arboard: Option<arboard::Clipboard>,
|
arboard: Option<arboard::Clipboard>,
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
|
@ -28,7 +28,7 @@ impl Clipboard {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub fn new(#[allow(unused_variables)] wayland_display: Option<*mut c_void>) -> Self {
|
pub fn new(#[allow(unused_variables)] wayland_display: Option<*mut c_void>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
#[cfg(feature = "arboard")]
|
#[cfg(all(feature = "arboard", not(target_os = "android")))]
|
||||||
arboard: init_arboard(),
|
arboard: init_arboard(),
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
any(
|
any(
|
||||||
|
@ -66,7 +66,7 @@ impl Clipboard {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "arboard")]
|
#[cfg(all(feature = "arboard", not(target_os = "android")))]
|
||||||
if let Some(clipboard) = &mut self.arboard {
|
if let Some(clipboard) = &mut self.arboard {
|
||||||
return match clipboard.get_text() {
|
return match clipboard.get_text() {
|
||||||
Ok(text) => Some(text),
|
Ok(text) => Some(text),
|
||||||
|
@ -96,7 +96,7 @@ impl Clipboard {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "arboard")]
|
#[cfg(all(feature = "arboard", not(target_os = "android")))]
|
||||||
if let Some(clipboard) = &mut self.arboard {
|
if let Some(clipboard) = &mut self.arboard {
|
||||||
if let Err(err) = clipboard.set_text(text) {
|
if let Err(err) = clipboard.set_text(text) {
|
||||||
tracing::error!("Copy/Cut error: {}", err);
|
tracing::error!("Copy/Cut error: {}", err);
|
||||||
|
@ -108,7 +108,7 @@ impl Clipboard {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "arboard")]
|
#[cfg(all(feature = "arboard", not(target_os = "android")))]
|
||||||
fn init_arboard() -> Option<arboard::Clipboard> {
|
fn init_arboard() -> Option<arboard::Clipboard> {
|
||||||
match arboard::Clipboard::new() {
|
match arboard::Clipboard::new() {
|
||||||
Ok(clipboard) => Some(clipboard),
|
Ok(clipboard) => Some(clipboard),
|
||||||
|
|
|
@ -66,7 +66,7 @@ pub struct State {
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub fn new<T>(event_loop: &EventLoopWindowTarget<T>) -> Self {
|
pub fn new<T>(event_loop: &EventLoopWindowTarget<T>) -> Self {
|
||||||
Self::new_with_wayland_display(get_wayland_display(event_loop))
|
Self::new_with_wayland_display(wayland_display(event_loop))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_wayland_display(wayland_display: Option<*mut c_void>) -> Self {
|
pub fn new_with_wayland_display(wayland_display: Option<*mut c_void>) -> Self {
|
||||||
|
@ -400,7 +400,7 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_mouse_wheel(&mut self, delta: winit::event::MouseScrollDelta) {
|
fn on_mouse_wheel(&mut self, delta: winit::event::MouseScrollDelta) {
|
||||||
let mut delta = match delta {
|
let delta = match delta {
|
||||||
winit::event::MouseScrollDelta::LineDelta(x, y) => {
|
winit::event::MouseScrollDelta::LineDelta(x, y) => {
|
||||||
let points_per_scroll_line = 50.0; // Scroll speed decided by consensus: https://github.com/emilk/egui/issues/461
|
let points_per_scroll_line = 50.0; // Scroll speed decided by consensus: https://github.com/emilk/egui/issues/461
|
||||||
egui::vec2(x, y) * points_per_scroll_line
|
egui::vec2(x, y) * points_per_scroll_line
|
||||||
|
@ -410,8 +410,6 @@ impl State {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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 {
|
if self.egui_input.modifiers.ctrl || self.egui_input.modifiers.command {
|
||||||
// Treat as zoom instead:
|
// Treat as zoom instead:
|
||||||
let factor = (delta.y / 200.0).exp();
|
let factor = (delta.y / 200.0).exp();
|
||||||
|
@ -712,7 +710,7 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option<winit::window::Curs
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a Wayland display handle if the target is running Wayland
|
/// Returns a Wayland display handle if the target is running Wayland
|
||||||
fn get_wayland_display<T>(_event_loop: &EventLoopWindowTarget<T>) -> Option<*mut c_void> {
|
fn wayland_display<T>(_event_loop: &EventLoopWindowTarget<T>) -> Option<*mut c_void> {
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
target_os = "linux",
|
target_os = "linux",
|
||||||
target_os = "dragonfly",
|
target_os = "dragonfly",
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
pub struct WindowSettings {
|
pub struct WindowSettings {
|
||||||
/// Position of window in physical pixels. This is either
|
/// Position of window in physical pixels. This is either
|
||||||
/// the inner or outer position depending on the platform.
|
/// the inner or outer position depending on the platform.
|
||||||
/// See [`winit::window::WindowAttributes`] for details.
|
/// See [`winit::window::WindowBuilder::with_position`] for details.
|
||||||
position: Option<egui::Pos2>,
|
position: Option<egui::Pos2>,
|
||||||
|
|
||||||
fullscreen: bool,
|
fullscreen: bool,
|
||||||
|
|
|
@ -57,14 +57,14 @@ serde = ["dep:serde", "epaint/serde"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
epaint = { version = "0.18.1", path = "../epaint", default-features = false }
|
epaint = { version = "0.18.1", path = "../epaint", default-features = false }
|
||||||
|
|
||||||
ahash = "0.7"
|
ahash = "0.8"
|
||||||
nohash-hasher = "0.2"
|
nohash-hasher = "0.2"
|
||||||
|
|
||||||
#! ### Optional dependencies
|
#! ### Optional dependencies
|
||||||
## Enable this when generating docs.
|
## Enable this when generating docs.
|
||||||
document-features = { version = "0.2", optional = true }
|
document-features = { version = "0.2", optional = true }
|
||||||
|
|
||||||
ron = { version = "0.7", optional = true }
|
ron = { version = "0.8", optional = true }
|
||||||
serde = { version = "1", optional = true, features = ["derive", "rc"] }
|
serde = { version = "1", optional = true, features = ["derive", "rc"] }
|
||||||
|
|
||||||
# egui doesn't log much, but when it does, it uses [`tracing`](https://docs.rs/tracing).
|
# egui doesn't log much, but when it does, it uses [`tracing`](https://docs.rs/tracing).
|
||||||
|
|
|
@ -46,7 +46,7 @@ impl Id {
|
||||||
/// Generate a new [`Id`] by hashing some source (e.g. a string or integer).
|
/// Generate a new [`Id`] by hashing some source (e.g. a string or integer).
|
||||||
pub fn new(source: impl std::hash::Hash) -> Id {
|
pub fn new(source: impl std::hash::Hash) -> Id {
|
||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
let mut hasher = epaint::ahash::AHasher::new_with_keys(123, 456);
|
let mut hasher = epaint::ahash::AHasher::default();
|
||||||
source.hash(&mut hasher);
|
source.hash(&mut hasher);
|
||||||
Id(hasher.finish())
|
Id(hasher.finish())
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ impl Id {
|
||||||
/// Generate a new [`Id`] by hashing the parent [`Id`] and the given argument.
|
/// Generate a new [`Id`] by hashing the parent [`Id`] and the given argument.
|
||||||
pub fn with(self, child: impl std::hash::Hash) -> Id {
|
pub fn with(self, child: impl std::hash::Hash) -> Id {
|
||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
let mut hasher = epaint::ahash::AHasher::new_with_keys(123, 456);
|
let mut hasher = epaint::ahash::AHasher::default();
|
||||||
hasher.write_u64(self.0);
|
hasher.write_u64(self.0);
|
||||||
child.hash(&mut hasher);
|
child.hash(&mut hasher);
|
||||||
Id(hasher.finish())
|
Id(hasher.finish())
|
||||||
|
|
|
@ -750,6 +750,20 @@ impl PointerState {
|
||||||
.any(|event| matches!(event, &PointerEvent::Pressed { button: b, .. } if button == b))
|
.any(|event| matches!(event, &PointerEvent::Pressed { button: b, .. } if button == b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Was the button given double clicked this frame?
|
||||||
|
pub fn button_double_clicked(&self, button: PointerButton) -> bool {
|
||||||
|
self.pointer_events
|
||||||
|
.iter()
|
||||||
|
.any(|event| matches!(&event, PointerEvent::Released(Some(click)) if click.button == button && click.is_double()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Was the button given triple clicked this frame?
|
||||||
|
pub fn button_triple_clicked(&self, button: PointerButton) -> bool {
|
||||||
|
self.pointer_events
|
||||||
|
.iter()
|
||||||
|
.any(|event| matches!(&event, PointerEvent::Released(Some(click)) if click.button == button && click.is_triple()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Was the primary button clicked this frame?
|
/// Was the primary button clicked this frame?
|
||||||
pub fn primary_clicked(&self) -> bool {
|
pub fn primary_clicked(&self) -> bool {
|
||||||
self.button_clicked(PointerButton::Primary)
|
self.button_clicked(PointerButton::Primary)
|
||||||
|
|
|
@ -252,7 +252,13 @@ impl Layout {
|
||||||
Self { main_wrap, ..self }
|
Self { main_wrap, ..self }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The aligmnet to use on the cross axis.
|
/// The alignment to use on the main axis.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn with_main_align(self, main_align: Align) -> Self {
|
||||||
|
Self { main_align, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The alignment to use on the cross axis.
|
||||||
///
|
///
|
||||||
/// The "cross" axis is the one orthogonal to the main axis.
|
/// The "cross" axis is the one orthogonal to the main axis.
|
||||||
/// For instance: in left-to-right layout, the main axis is horizontal and the cross axis is vertical.
|
/// For instance: in left-to-right layout, the main axis is horizontal and the cross axis is vertical.
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use epaint::ahash::AHashSet;
|
|
||||||
|
|
||||||
use crate::{area, window, Id, IdMap, InputState, LayerId, Pos2, Rect, Style};
|
use crate::{area, window, Id, IdMap, InputState, LayerId, Pos2, Rect, Style};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -490,15 +488,15 @@ pub struct Areas {
|
||||||
areas: IdMap<area::State>,
|
areas: IdMap<area::State>,
|
||||||
/// Back-to-front. Top is last.
|
/// Back-to-front. Top is last.
|
||||||
order: Vec<LayerId>,
|
order: Vec<LayerId>,
|
||||||
visible_last_frame: AHashSet<LayerId>,
|
visible_last_frame: ahash::HashSet<LayerId>,
|
||||||
visible_current_frame: AHashSet<LayerId>,
|
visible_current_frame: ahash::HashSet<LayerId>,
|
||||||
|
|
||||||
/// When an area want to be on top, it is put in here.
|
/// When an area want to be on top, it is put in here.
|
||||||
/// At the end of the frame, this is used to reorder the layers.
|
/// At the end of the frame, this is used to reorder the layers.
|
||||||
/// This means if several layers want to be on top, they will keep their relative order.
|
/// This means if several layers want to be on top, they will keep their relative order.
|
||||||
/// So if you close three windows and then reopen them all in one frame,
|
/// So if you close three windows and then reopen them all in one frame,
|
||||||
/// they will all be sent to the top, but keep their previous internal order.
|
/// they will all be sent to the top, but keep their previous internal order.
|
||||||
wants_to_be_on_top: AHashSet<LayerId>,
|
wants_to_be_on_top: ahash::HashSet<LayerId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Areas {
|
impl Areas {
|
||||||
|
@ -550,7 +548,7 @@ impl Areas {
|
||||||
self.visible_last_frame.contains(layer_id) || self.visible_current_frame.contains(layer_id)
|
self.visible_last_frame.contains(layer_id) || self.visible_current_frame.contains(layer_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn visible_layer_ids(&self) -> AHashSet<LayerId> {
|
pub fn visible_layer_ids(&self) -> ahash::HashSet<LayerId> {
|
||||||
self.visible_last_frame
|
self.visible_last_frame
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
|
|
|
@ -532,7 +532,7 @@ impl MenuState {
|
||||||
id: Id,
|
id: Id,
|
||||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
) -> Option<R> {
|
) -> Option<R> {
|
||||||
let (sub_response, response) = self.get_submenu(id).map(|sub| {
|
let (sub_response, response) = self.submenu(id).map(|sub| {
|
||||||
let inner_response = Self::show(ctx, sub, id, add_contents);
|
let inner_response = Self::show(ctx, sub, id, add_contents);
|
||||||
(sub.read().response, inner_response.inner)
|
(sub.read().response, inner_response.inner)
|
||||||
})?;
|
})?;
|
||||||
|
@ -582,7 +582,7 @@ impl MenuState {
|
||||||
if pointer.is_still() {
|
if pointer.is_still() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if let Some(sub_menu) = self.get_current_submenu() {
|
if let Some(sub_menu) = self.current_submenu() {
|
||||||
if let Some(pos) = pointer.hover_pos() {
|
if let Some(pos) = pointer.hover_pos() {
|
||||||
return Self::points_at_left_of_rect(pos, pointer.velocity(), sub_menu.read().rect);
|
return Self::points_at_left_of_rect(pos, pointer.velocity(), sub_menu.read().rect);
|
||||||
}
|
}
|
||||||
|
@ -592,7 +592,7 @@ impl MenuState {
|
||||||
|
|
||||||
/// Check if pointer is hovering current submenu.
|
/// Check if pointer is hovering current submenu.
|
||||||
fn hovering_current_submenu(&self, pointer: &PointerState) -> bool {
|
fn hovering_current_submenu(&self, pointer: &PointerState) -> bool {
|
||||||
if let Some(sub_menu) = self.get_current_submenu() {
|
if let Some(sub_menu) = self.current_submenu() {
|
||||||
if let Some(pos) = pointer.hover_pos() {
|
if let Some(pos) = pointer.hover_pos() {
|
||||||
return sub_menu.read().area_contains(pos);
|
return sub_menu.read().area_contains(pos);
|
||||||
}
|
}
|
||||||
|
@ -608,18 +608,18 @@ impl MenuState {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_open(&self, id: Id) -> bool {
|
fn is_open(&self, id: Id) -> bool {
|
||||||
self.get_sub_id() == Some(id)
|
self.sub_id() == Some(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sub_id(&self) -> Option<Id> {
|
fn sub_id(&self) -> Option<Id> {
|
||||||
self.sub_menu.as_ref().map(|(id, _)| *id)
|
self.sub_menu.as_ref().map(|(id, _)| *id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_current_submenu(&self) -> Option<&Arc<RwLock<MenuState>>> {
|
fn current_submenu(&self) -> Option<&Arc<RwLock<MenuState>>> {
|
||||||
self.sub_menu.as_ref().map(|(_, sub)| sub)
|
self.sub_menu.as_ref().map(|(_, sub)| sub)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_submenu(&mut self, id: Id) -> Option<&Arc<RwLock<MenuState>>> {
|
fn submenu(&mut self, id: Id) -> Option<&Arc<RwLock<MenuState>>> {
|
||||||
self.sub_menu
|
self.sub_menu
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|(k, sub)| if id == *k { Some(sub) } else { None })
|
.and_then(|(k, sub)| if id == *k { Some(sub) } else { None })
|
||||||
|
|
|
@ -99,7 +99,7 @@ impl Ui {
|
||||||
crate::egui_assert!(!max_rect.any_nan());
|
crate::egui_assert!(!max_rect.any_nan());
|
||||||
let next_auto_id_source = Id::new(self.next_auto_id_source).with("child").value();
|
let next_auto_id_source = Id::new(self.next_auto_id_source).with("child").value();
|
||||||
self.next_auto_id_source = self.next_auto_id_source.wrapping_add(1);
|
self.next_auto_id_source = self.next_auto_id_source.wrapping_add(1);
|
||||||
let menu_state = self.get_menu_state();
|
let menu_state = self.menu_state();
|
||||||
Ui {
|
Ui {
|
||||||
id: self.id.with(id_source),
|
id: self.id.with(id_source),
|
||||||
next_auto_id_source,
|
next_auto_id_source,
|
||||||
|
@ -2096,7 +2096,7 @@ impl Ui {
|
||||||
self.menu_state = None;
|
self.menu_state = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_menu_state(&self) -> Option<Arc<RwLock<MenuState>>> {
|
pub(crate) fn menu_state(&self) -> Option<Arc<RwLock<MenuState>>> {
|
||||||
self.menu_state.clone()
|
self.menu_state.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -119,7 +119,7 @@ impl<Value: 'static + Send + Sync, Computer: 'static + Send + Sync> CacheTrait
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CacheStorage {
|
pub struct CacheStorage {
|
||||||
caches: ahash::AHashMap<std::any::TypeId, Box<dyn CacheTrait>>,
|
caches: ahash::HashMap<std::any::TypeId, Box<dyn CacheTrait>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CacheStorage {
|
impl CacheStorage {
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub(super) struct PlotConfig<'a> {
|
||||||
|
|
||||||
/// Trait shared by things that can be drawn in the plot.
|
/// Trait shared by things that can be drawn in the plot.
|
||||||
pub(super) trait PlotItem {
|
pub(super) trait PlotItem {
|
||||||
fn get_shapes(&self, ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>);
|
fn shapes(&self, ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>);
|
||||||
|
|
||||||
fn initialize(&mut self, x_range: RangeInclusive<f64>);
|
fn initialize(&mut self, x_range: RangeInclusive<f64>);
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ pub(super) trait PlotItem {
|
||||||
|
|
||||||
fn geometry(&self) -> PlotGeometry<'_>;
|
fn geometry(&self) -> PlotGeometry<'_>;
|
||||||
|
|
||||||
fn get_bounds(&self) -> PlotBounds;
|
fn bounds(&self) -> PlotBounds;
|
||||||
|
|
||||||
fn find_closest(&self, point: Pos2, transform: &ScreenTransform) -> Option<ClosestElem> {
|
fn find_closest(&self, point: Pos2, transform: &ScreenTransform) -> Option<ClosestElem> {
|
||||||
match self.geometry() {
|
match self.geometry() {
|
||||||
|
@ -167,7 +167,7 @@ impl HLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for HLine {
|
impl PlotItem for HLine {
|
||||||
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||||
let HLine {
|
let HLine {
|
||||||
y,
|
y,
|
||||||
stroke,
|
stroke,
|
||||||
|
@ -204,7 +204,7 @@ impl PlotItem for HLine {
|
||||||
PlotGeometry::None
|
PlotGeometry::None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> PlotBounds {
|
fn bounds(&self) -> PlotBounds {
|
||||||
let mut bounds = PlotBounds::NOTHING;
|
let mut bounds = PlotBounds::NOTHING;
|
||||||
bounds.min[1] = self.y;
|
bounds.min[1] = self.y;
|
||||||
bounds.max[1] = self.y;
|
bounds.max[1] = self.y;
|
||||||
|
@ -277,7 +277,7 @@ impl VLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for VLine {
|
impl PlotItem for VLine {
|
||||||
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||||
let VLine {
|
let VLine {
|
||||||
x,
|
x,
|
||||||
stroke,
|
stroke,
|
||||||
|
@ -314,7 +314,7 @@ impl PlotItem for VLine {
|
||||||
PlotGeometry::None
|
PlotGeometry::None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> PlotBounds {
|
fn bounds(&self) -> PlotBounds {
|
||||||
let mut bounds = PlotBounds::NOTHING;
|
let mut bounds = PlotBounds::NOTHING;
|
||||||
bounds.min[0] = self.x;
|
bounds.min[0] = self.x;
|
||||||
bounds.max[0] = self.x;
|
bounds.max[0] = self.x;
|
||||||
|
@ -401,7 +401,7 @@ fn y_intersection(p1: &Pos2, p2: &Pos2, y: f32) -> Option<f32> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for Line {
|
impl PlotItem for Line {
|
||||||
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||||
let Self {
|
let Self {
|
||||||
series,
|
series,
|
||||||
stroke,
|
stroke,
|
||||||
|
@ -484,8 +484,8 @@ impl PlotItem for Line {
|
||||||
PlotGeometry::Points(self.series.points())
|
PlotGeometry::Points(self.series.points())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> PlotBounds {
|
fn bounds(&self) -> PlotBounds {
|
||||||
self.series.get_bounds()
|
self.series.bounds()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -562,7 +562,7 @@ impl Polygon {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for Polygon {
|
impl PlotItem for Polygon {
|
||||||
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||||
let Self {
|
let Self {
|
||||||
series,
|
series,
|
||||||
stroke,
|
stroke,
|
||||||
|
@ -614,8 +614,8 @@ impl PlotItem for Polygon {
|
||||||
PlotGeometry::Points(self.series.points())
|
PlotGeometry::Points(self.series.points())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> PlotBounds {
|
fn bounds(&self) -> PlotBounds {
|
||||||
self.series.get_bounds()
|
self.series.bounds()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -674,7 +674,7 @@ impl Text {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for Text {
|
impl PlotItem for Text {
|
||||||
fn get_shapes(&self, ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||||
let color = if self.color == Color32::TRANSPARENT {
|
let color = if self.color == Color32::TRANSPARENT {
|
||||||
ui.style().visuals.text_color()
|
ui.style().visuals.text_color()
|
||||||
} else {
|
} else {
|
||||||
|
@ -728,7 +728,7 @@ impl PlotItem for Text {
|
||||||
PlotGeometry::None
|
PlotGeometry::None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> PlotBounds {
|
fn bounds(&self) -> PlotBounds {
|
||||||
let mut bounds = PlotBounds::NOTHING;
|
let mut bounds = PlotBounds::NOTHING;
|
||||||
bounds.extend_with(&self.position);
|
bounds.extend_with(&self.position);
|
||||||
bounds
|
bounds
|
||||||
|
@ -814,7 +814,7 @@ impl Points {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for Points {
|
impl PlotItem for Points {
|
||||||
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||||
let sqrt_3 = 3_f32.sqrt();
|
let sqrt_3 = 3_f32.sqrt();
|
||||||
let frac_sqrt_3_2 = 3_f32.sqrt() / 2.0;
|
let frac_sqrt_3_2 = 3_f32.sqrt() / 2.0;
|
||||||
let frac_1_sqrt_2 = 1.0 / 2_f32.sqrt();
|
let frac_1_sqrt_2 = 1.0 / 2_f32.sqrt();
|
||||||
|
@ -965,8 +965,8 @@ impl PlotItem for Points {
|
||||||
PlotGeometry::Points(self.series.points())
|
PlotGeometry::Points(self.series.points())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> PlotBounds {
|
fn bounds(&self) -> PlotBounds {
|
||||||
self.series.get_bounds()
|
self.series.bounds()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1016,7 +1016,7 @@ impl Arrows {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for Arrows {
|
impl PlotItem for Arrows {
|
||||||
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||||
use crate::emath::*;
|
use crate::emath::*;
|
||||||
let Self {
|
let Self {
|
||||||
origins,
|
origins,
|
||||||
|
@ -1080,8 +1080,8 @@ impl PlotItem for Arrows {
|
||||||
PlotGeometry::Points(self.origins.points())
|
PlotGeometry::Points(self.origins.points())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> PlotBounds {
|
fn bounds(&self) -> PlotBounds {
|
||||||
self.origins.get_bounds()
|
self.origins.bounds()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1155,7 +1155,7 @@ impl PlotImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for PlotImage {
|
impl PlotItem for PlotImage {
|
||||||
fn get_shapes(&self, ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||||
let Self {
|
let Self {
|
||||||
position,
|
position,
|
||||||
texture_id,
|
texture_id,
|
||||||
|
@ -1215,7 +1215,7 @@ impl PlotItem for PlotImage {
|
||||||
PlotGeometry::None
|
PlotGeometry::None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> PlotBounds {
|
fn bounds(&self) -> PlotBounds {
|
||||||
let mut bounds = PlotBounds::NOTHING;
|
let mut bounds = PlotBounds::NOTHING;
|
||||||
let left_top = PlotPoint::new(
|
let left_top = PlotPoint::new(
|
||||||
self.position.x as f32 - self.size.x / 2.0,
|
self.position.x as f32 - self.size.x / 2.0,
|
||||||
|
@ -1346,7 +1346,7 @@ impl BarChart {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for BarChart {
|
impl PlotItem for BarChart {
|
||||||
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||||
for b in &self.bars {
|
for b in &self.bars {
|
||||||
b.add_shapes(transform, self.highlight, shapes);
|
b.add_shapes(transform, self.highlight, shapes);
|
||||||
}
|
}
|
||||||
|
@ -1376,7 +1376,7 @@ impl PlotItem for BarChart {
|
||||||
PlotGeometry::Rects
|
PlotGeometry::Rects
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> PlotBounds {
|
fn bounds(&self) -> PlotBounds {
|
||||||
let mut bounds = PlotBounds::NOTHING;
|
let mut bounds = PlotBounds::NOTHING;
|
||||||
for b in &self.bars {
|
for b in &self.bars {
|
||||||
bounds.merge(&b.bounds());
|
bounds.merge(&b.bounds());
|
||||||
|
@ -1488,7 +1488,7 @@ impl BoxPlot {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for BoxPlot {
|
impl PlotItem for BoxPlot {
|
||||||
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||||
for b in &self.boxes {
|
for b in &self.boxes {
|
||||||
b.add_shapes(transform, self.highlight, shapes);
|
b.add_shapes(transform, self.highlight, shapes);
|
||||||
}
|
}
|
||||||
|
@ -1518,7 +1518,7 @@ impl PlotItem for BoxPlot {
|
||||||
PlotGeometry::Rects
|
PlotGeometry::Rects
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> PlotBounds {
|
fn bounds(&self) -> PlotBounds {
|
||||||
let mut bounds = PlotBounds::NOTHING;
|
let mut bounds = PlotBounds::NOTHING;
|
||||||
for b in &self.boxes {
|
for b in &self.boxes {
|
||||||
bounds.merge(&b.bounds());
|
bounds.merge(&b.bounds());
|
||||||
|
|
|
@ -302,7 +302,7 @@ impl PlotPoints {
|
||||||
(start < end).then(|| start..=end)
|
(start < end).then(|| start..=end)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn get_bounds(&self) -> PlotBounds {
|
pub(super) fn bounds(&self) -> PlotBounds {
|
||||||
match self {
|
match self {
|
||||||
PlotPoints::Owned(points) => {
|
PlotPoints::Owned(points) => {
|
||||||
let mut bounds = PlotBounds::NOTHING;
|
let mut bounds = PlotBounds::NOTHING;
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use std::{collections::BTreeMap, string::String};
|
use std::{collections::BTreeMap, string::String};
|
||||||
|
|
||||||
use epaint::ahash::AHashSet;
|
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
use super::items::PlotItem;
|
use super::items::PlotItem;
|
||||||
|
@ -168,7 +166,7 @@ impl LegendWidget {
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
config: Legend,
|
config: Legend,
|
||||||
items: &[Box<dyn PlotItem>],
|
items: &[Box<dyn PlotItem>],
|
||||||
hidden_items: &AHashSet<String>,
|
hidden_items: &ahash::HashSet<String>,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
// Collect the legend entries. If multiple items have the same name, they share a
|
// Collect the legend entries. If multiple items have the same name, they share a
|
||||||
// checkbox. If their colors don't match, we pick a neutral color for the checkbox.
|
// checkbox. If their colors don't match, we pick a neutral color for the checkbox.
|
||||||
|
@ -199,7 +197,7 @@ impl LegendWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the names of the hidden items.
|
// Get the names of the hidden items.
|
||||||
pub fn get_hidden_items(&self) -> AHashSet<String> {
|
pub fn hidden_items(&self) -> ahash::HashSet<String> {
|
||||||
self.entries
|
self.entries
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, entry)| !entry.checked)
|
.filter(|(_, entry)| !entry.checked)
|
||||||
|
@ -208,7 +206,7 @@ impl LegendWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the name of the hovered items.
|
// Get the name of the hovered items.
|
||||||
pub fn get_hovered_entry_name(&self) -> Option<String> {
|
pub fn hovered_entry_name(&self) -> Option<String> {
|
||||||
self.entries
|
self.entries
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(_, entry)| entry.hovered)
|
.find(|(_, entry)| entry.hovered)
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
use std::{cell::Cell, ops::RangeInclusive, rc::Rc};
|
use std::{cell::Cell, ops::RangeInclusive, rc::Rc};
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use epaint::ahash::AHashSet;
|
|
||||||
use epaint::color::Hsva;
|
use epaint::color::Hsva;
|
||||||
use epaint::util::FloatOrd;
|
use epaint::util::FloatOrd;
|
||||||
|
|
||||||
|
@ -96,7 +95,7 @@ impl From<bool> for AutoBounds {
|
||||||
struct PlotMemory {
|
struct PlotMemory {
|
||||||
auto_bounds: AutoBounds,
|
auto_bounds: AutoBounds,
|
||||||
hovered_entry: Option<String>,
|
hovered_entry: Option<String>,
|
||||||
hidden_items: AHashSet<String>,
|
hidden_items: ahash::HashSet<String>,
|
||||||
min_auto_bounds: PlotBounds,
|
min_auto_bounds: PlotBounds,
|
||||||
last_screen_transform: ScreenTransform,
|
last_screen_transform: ScreenTransform,
|
||||||
/// Allows to remember the first click position when performing a boxed zoom
|
/// Allows to remember the first click position when performing a boxed zoom
|
||||||
|
@ -430,7 +429,7 @@ impl Plot {
|
||||||
///
|
///
|
||||||
/// The function has this signature:
|
/// The function has this signature:
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// fn get_step_sizes(input: GridInput) -> Vec<GridMark>;
|
/// fn step_sizes(input: GridInput) -> Vec<GridMark>;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// This function should return all marks along the visible range of the X axis.
|
/// This function should return all marks along the visible range of the X axis.
|
||||||
|
@ -697,7 +696,7 @@ impl Plot {
|
||||||
}
|
}
|
||||||
|
|
||||||
for item in &items {
|
for item in &items {
|
||||||
let item_bounds = item.get_bounds();
|
let item_bounds = item.bounds();
|
||||||
|
|
||||||
if auto_bounds.x {
|
if auto_bounds.x {
|
||||||
bounds.merge_x(&item_bounds);
|
bounds.merge_x(&item_bounds);
|
||||||
|
@ -829,8 +828,8 @@ impl Plot {
|
||||||
|
|
||||||
if let Some(mut legend) = legend {
|
if let Some(mut legend) = legend {
|
||||||
ui.add(&mut legend);
|
ui.add(&mut legend);
|
||||||
hidden_items = legend.get_hidden_items();
|
hidden_items = legend.hidden_items();
|
||||||
hovered_entry = legend.get_hovered_entry_name();
|
hovered_entry = legend.hovered_entry_name();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(group) = linked_axes.as_ref() {
|
if let Some(group) = linked_axes.as_ref() {
|
||||||
|
@ -1073,7 +1072,7 @@ pub struct GridMark {
|
||||||
/// 10 is a typical value, others are possible though.
|
/// 10 is a typical value, others are possible though.
|
||||||
pub fn log_grid_spacer(log_base: i64) -> GridSpacer {
|
pub fn log_grid_spacer(log_base: i64) -> GridSpacer {
|
||||||
let log_base = log_base as f64;
|
let log_base = log_base as f64;
|
||||||
let get_step_sizes = move |input: GridInput| -> Vec<GridMark> {
|
let step_sizes = move |input: GridInput| -> Vec<GridMark> {
|
||||||
// The distance between two of the thinnest grid lines is "rounded" up
|
// The distance between two of the thinnest grid lines is "rounded" up
|
||||||
// to the next-bigger power of base
|
// to the next-bigger power of base
|
||||||
let smallest_visible_unit = next_power(input.base_step_size, log_base);
|
let smallest_visible_unit = next_power(input.base_step_size, log_base);
|
||||||
|
@ -1087,7 +1086,7 @@ pub fn log_grid_spacer(log_base: i64) -> GridSpacer {
|
||||||
generate_marks(step_sizes, input.bounds)
|
generate_marks(step_sizes, input.bounds)
|
||||||
};
|
};
|
||||||
|
|
||||||
Box::new(get_step_sizes)
|
Box::new(step_sizes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Splits the grid into uniform-sized spacings (e.g. 100, 25, 1).
|
/// Splits the grid into uniform-sized spacings (e.g. 100, 25, 1).
|
||||||
|
@ -1136,7 +1135,7 @@ impl PreparedPlot {
|
||||||
let mut plot_ui = ui.child_ui(*transform.frame(), Layout::default());
|
let mut plot_ui = ui.child_ui(*transform.frame(), Layout::default());
|
||||||
plot_ui.set_clip_rect(*transform.frame());
|
plot_ui.set_clip_rect(*transform.frame());
|
||||||
for item in &self.items {
|
for item in &self.items {
|
||||||
item.get_shapes(&mut plot_ui, transform, &mut shapes);
|
item.shapes(&mut plot_ui, transform, &mut shapes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(pointer) = response.hover_pos() {
|
if let Some(pointer) = response.hover_pos() {
|
||||||
|
|
|
@ -16,9 +16,9 @@ impl Custom3d {
|
||||||
pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Self {
|
pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Self {
|
||||||
// Get the WGPU render state from the eframe creation context. This can also be retrieved
|
// Get the WGPU render state from the eframe creation context. This can also be retrieved
|
||||||
// from `eframe::Frame` when you don't have a `CreationContext` available.
|
// from `eframe::Frame` when you don't have a `CreationContext` available.
|
||||||
let render_state = cc.render_state.as_ref().expect("WGPU enabled");
|
let wgpu_render_state = cc.wgpu_render_state.as_ref().expect("WGPU enabled");
|
||||||
|
|
||||||
let device = &render_state.device;
|
let device = &wgpu_render_state.device;
|
||||||
|
|
||||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
|
@ -56,7 +56,7 @@ impl Custom3d {
|
||||||
fragment: Some(wgpu::FragmentState {
|
fragment: Some(wgpu::FragmentState {
|
||||||
module: &shader,
|
module: &shader,
|
||||||
entry_point: "fs_main",
|
entry_point: "fs_main",
|
||||||
targets: &[Some(render_state.target_format.into())],
|
targets: &[Some(wgpu_render_state.target_format.into())],
|
||||||
}),
|
}),
|
||||||
primitive: wgpu::PrimitiveState::default(),
|
primitive: wgpu::PrimitiveState::default(),
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
|
@ -84,7 +84,7 @@ impl Custom3d {
|
||||||
// Because the graphics pipeline must have the same lifetime as the egui render pass,
|
// Because the graphics pipeline must have the same lifetime as the egui render pass,
|
||||||
// instead of storing the pipeline in our `Custom3D` struct, we insert it into the
|
// instead of storing the pipeline in our `Custom3D` struct, we insert it into the
|
||||||
// `paint_callback_resources` type map, which is stored alongside the render pass.
|
// `paint_callback_resources` type map, which is stored alongside the render pass.
|
||||||
render_state
|
wgpu_render_state
|
||||||
.egui_rpass
|
.egui_rpass
|
||||||
.write()
|
.write()
|
||||||
.paint_callback_resources
|
.paint_callback_resources
|
||||||
|
|
|
@ -389,19 +389,19 @@ impl CustomAxisDemo {
|
||||||
const MINS_PER_DAY: f64 = CustomAxisDemo::MINS_PER_DAY;
|
const MINS_PER_DAY: f64 = CustomAxisDemo::MINS_PER_DAY;
|
||||||
const MINS_PER_H: f64 = CustomAxisDemo::MINS_PER_H;
|
const MINS_PER_H: f64 = CustomAxisDemo::MINS_PER_H;
|
||||||
|
|
||||||
fn get_day(x: f64) -> f64 {
|
fn day(x: f64) -> f64 {
|
||||||
(x / MINS_PER_DAY).floor()
|
(x / MINS_PER_DAY).floor()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_hour(x: f64) -> f64 {
|
fn hour(x: f64) -> f64 {
|
||||||
(x.rem_euclid(MINS_PER_DAY) / MINS_PER_H).floor()
|
(x.rem_euclid(MINS_PER_DAY) / MINS_PER_H).floor()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_minute(x: f64) -> f64 {
|
fn minute(x: f64) -> f64 {
|
||||||
x.rem_euclid(MINS_PER_H).floor()
|
x.rem_euclid(MINS_PER_H).floor()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_percent(y: f64) -> f64 {
|
fn percent(y: f64) -> f64 {
|
||||||
100.0 * y
|
100.0 * y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,17 +411,17 @@ impl CustomAxisDemo {
|
||||||
String::new()
|
String::new()
|
||||||
} else if is_approx_integer(x / MINS_PER_DAY) {
|
} else if is_approx_integer(x / MINS_PER_DAY) {
|
||||||
// Days
|
// Days
|
||||||
format!("Day {}", get_day(x))
|
format!("Day {}", day(x))
|
||||||
} else {
|
} else {
|
||||||
// Hours and minutes
|
// Hours and minutes
|
||||||
format!("{h}:{m:02}", h = get_hour(x), m = get_minute(x))
|
format!("{h}:{m:02}", h = hour(x), m = minute(x))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let y_fmt = |y, _range: &RangeInclusive<f64>| {
|
let y_fmt = |y, _range: &RangeInclusive<f64>| {
|
||||||
// Display only integer percentages
|
// Display only integer percentages
|
||||||
if !is_approx_zero(y) && is_approx_integer(100.0 * y) {
|
if !is_approx_zero(y) && is_approx_integer(100.0 * y) {
|
||||||
format!("{:.0}%", get_percent(y))
|
format!("{:.0}%", percent(y))
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
}
|
}
|
||||||
|
@ -430,10 +430,10 @@ impl CustomAxisDemo {
|
||||||
let label_fmt = |_s: &str, val: &PlotPoint| {
|
let label_fmt = |_s: &str, val: &PlotPoint| {
|
||||||
format!(
|
format!(
|
||||||
"Day {d}, {h}:{m:02}\n{p:.2}%",
|
"Day {d}, {h}:{m:02}\n{p:.2}%",
|
||||||
d = get_day(val.x),
|
d = day(val.x),
|
||||||
h = get_hour(val.x),
|
h = hour(val.x),
|
||||||
m = get_minute(val.x),
|
m = minute(val.x),
|
||||||
p = get_percent(val.y)
|
p = percent(val.y)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ image = { version = "0.24", optional = true, default-features = false }
|
||||||
|
|
||||||
# svg feature
|
# svg feature
|
||||||
resvg = { version = "0.23", optional = true }
|
resvg = { version = "0.23", optional = true }
|
||||||
tiny-skia = { version = "0.6", optional = true }
|
tiny-skia = { version = "0.6", optional = true } # must be updated in lock-step with resvg
|
||||||
usvg = { version = "0.23", optional = true }
|
usvg = { version = "0.23", optional = true }
|
||||||
|
|
||||||
# feature "serde":
|
# feature "serde":
|
||||||
|
|
|
@ -44,9 +44,9 @@ egui = { version = "0.18.0", path = "../egui", default-features = false, feature
|
||||||
] }
|
] }
|
||||||
egui-winit = { version = "0.18.0", path = "../egui-winit", default-features = false }
|
egui-winit = { version = "0.18.0", path = "../egui-winit", default-features = false }
|
||||||
|
|
||||||
ahash = "0.7"
|
ahash = "0.8"
|
||||||
bytemuck = "1.7"
|
bytemuck = "1.7"
|
||||||
glium = "0.31"
|
glium = "0.32"
|
||||||
|
|
||||||
#! ### Optional dependencies
|
#! ### Optional dependencies
|
||||||
## Enable this when generating docs.
|
## Enable this when generating docs.
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use glium::glutin;
|
use glium::glutin;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let event_loop = glutin::event_loop::EventLoop::with_user_event();
|
let event_loop = glutin::event_loop::EventLoopBuilder::with_user_event().build();
|
||||||
let display = create_display(&event_loop);
|
let display = create_display(&event_loop);
|
||||||
|
|
||||||
let mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop);
|
let mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop);
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
use glium::glutin;
|
use glium::glutin;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let event_loop = glutin::event_loop::EventLoop::with_user_event();
|
let event_loop = glutin::event_loop::EventLoopBuilder::with_user_event().build();
|
||||||
let display = create_display(&event_loop);
|
let display = create_display(&event_loop);
|
||||||
|
|
||||||
let mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop);
|
let mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop);
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
use egui::epaint::Primitive;
|
use egui::epaint::Primitive;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
ahash::AHashMap,
|
|
||||||
egui::{emath::Rect, epaint::Mesh},
|
egui::{emath::Rect, epaint::Mesh},
|
||||||
glium::{
|
glium::{
|
||||||
implement_vertex,
|
implement_vertex,
|
||||||
|
@ -21,7 +20,7 @@ pub struct Painter {
|
||||||
max_texture_side: usize,
|
max_texture_side: usize,
|
||||||
program: glium::Program,
|
program: glium::Program,
|
||||||
|
|
||||||
textures: AHashMap<egui::TextureId, Rc<SrgbTexture2d>>,
|
textures: ahash::HashMap<egui::TextureId, Rc<SrgbTexture2d>>,
|
||||||
|
|
||||||
/// [`egui::TextureId::User`] index
|
/// [`egui::TextureId::User`] index
|
||||||
next_native_tex_id: u64,
|
next_native_tex_id: u64,
|
||||||
|
@ -145,7 +144,7 @@ impl Painter {
|
||||||
let width_in_points = width_in_pixels as f32 / pixels_per_point;
|
let width_in_points = width_in_pixels as f32 / pixels_per_point;
|
||||||
let height_in_points = height_in_pixels as f32 / pixels_per_point;
|
let height_in_points = height_in_pixels as f32 / pixels_per_point;
|
||||||
|
|
||||||
if let Some(texture) = self.get_texture(mesh.texture_id) {
|
if let Some(texture) = self.texture(mesh.texture_id) {
|
||||||
// The texture coordinates for text are so that both nearest and linear should work with the egui font texture.
|
// The texture coordinates for text are so that both nearest and linear should work with the egui font texture.
|
||||||
// For user textures linear sampling is more likely to be the right choice.
|
// For user textures linear sampling is more likely to be the right choice.
|
||||||
let filter = MagnifySamplerFilter::Linear;
|
let filter = MagnifySamplerFilter::Linear;
|
||||||
|
@ -274,7 +273,7 @@ impl Painter {
|
||||||
self.textures.remove(&tex_id);
|
self.textures.remove(&tex_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
|
fn texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
|
||||||
self.textures.get(&texture_id).map(|rc| rc.as_ref())
|
self.textures.get(&texture_id).map(|rc| rc.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ wasm-bindgen = { version = "0.2" }
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
glutin = "0.28.0" # examples/pure_glow
|
glutin = "0.29.0" # examples/pure_glow
|
||||||
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut clear_color = [0.1, 0.1, 0.1];
|
let mut clear_color = [0.1, 0.1, 0.1];
|
||||||
|
|
||||||
let event_loop = glutin::event_loop::EventLoop::with_user_event();
|
let event_loop = glutin::event_loop::EventLoopBuilder::with_user_event().build();
|
||||||
let (gl_window, gl) = create_display(&event_loop);
|
let (gl_window, gl) = create_display(&event_loop);
|
||||||
let gl = std::sync::Arc::new(gl);
|
let gl = std::sync::Arc::new(gl);
|
||||||
|
|
||||||
|
|
|
@ -452,7 +452,7 @@ impl Painter {
|
||||||
#[inline(never)] // Easier profiling
|
#[inline(never)] // Easier profiling
|
||||||
fn paint_mesh(&mut self, mesh: &Mesh) {
|
fn paint_mesh(&mut self, mesh: &Mesh) {
|
||||||
debug_assert!(mesh.is_valid());
|
debug_assert!(mesh.is_valid());
|
||||||
if let Some(texture) = self.get_texture(mesh.texture_id) {
|
if let Some(texture) = self.texture(mesh.texture_id) {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
|
self.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
|
||||||
self.gl.buffer_data_u8_slice(
|
self.gl.buffer_data_u8_slice(
|
||||||
|
@ -634,10 +634,15 @@ impl Painter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the [`glow::Texture`] bound to a [`egui::TextureId`].
|
/// Get the [`glow::Texture`] bound to a [`egui::TextureId`].
|
||||||
pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<glow::Texture> {
|
pub fn texture(&self, texture_id: egui::TextureId) -> Option<glow::Texture> {
|
||||||
self.textures.get(&texture_id).copied()
|
self.textures.get(&texture_id).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "renamed 'texture'"]
|
||||||
|
pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<glow::Texture> {
|
||||||
|
self.texture(texture_id)
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::needless_pass_by_value)] // False positive
|
#[allow(clippy::needless_pass_by_value)] // False positive
|
||||||
pub fn register_native_texture(&mut self, native: glow::Texture) -> egui::TextureId {
|
pub fn register_native_texture(&mut self, native: glow::Texture) -> egui::TextureId {
|
||||||
self.assert_not_destroyed();
|
self.assert_not_destroyed();
|
||||||
|
|
|
@ -56,7 +56,7 @@ serde = ["dep:serde", "ahash/serde", "emath/serde"]
|
||||||
emath = { version = "0.18.0", path = "../emath" }
|
emath = { version = "0.18.0", path = "../emath" }
|
||||||
|
|
||||||
ab_glyph = "0.2.11"
|
ab_glyph = "0.2.11"
|
||||||
ahash = { version = "0.7", default-features = false, features = ["std"] }
|
ahash = { version = "0.8", default-features = false, features = ["std"] }
|
||||||
nohash-hasher = "0.2"
|
nohash-hasher = "0.2"
|
||||||
|
|
||||||
#! ### Optional dependencies
|
#! ### Optional dependencies
|
||||||
|
|
|
@ -13,6 +13,11 @@ pub use crate::{CubicBezierShape, QuadraticBezierShape};
|
||||||
|
|
||||||
/// A paint primitive such as a circle or a piece of text.
|
/// A paint primitive such as a circle or a piece of text.
|
||||||
/// Coordinates are all screen space points (not physical pixels).
|
/// Coordinates are all screen space points (not physical pixels).
|
||||||
|
///
|
||||||
|
/// You should generally recreate your [`Shape`]s each frame,
|
||||||
|
/// but storing them should also be fine with one exception:
|
||||||
|
/// [`Shape::Text`] depends on the current `pixels_per_point` (dpi scale)
|
||||||
|
/// and so must be recreated every time `pixels_per_point` changes.
|
||||||
#[must_use = "Add a Shape to a Painter"]
|
#[must_use = "Add a Shape to a Painter"]
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Shape {
|
pub enum Shape {
|
||||||
|
@ -37,6 +42,8 @@ pub enum Shape {
|
||||||
Rect(RectShape),
|
Rect(RectShape),
|
||||||
|
|
||||||
/// Text.
|
/// Text.
|
||||||
|
///
|
||||||
|
/// This needs to be recreated if `pixels_per_point` (dpi scale) changes.
|
||||||
Text(TextShape),
|
Text(TextShape),
|
||||||
|
|
||||||
/// A general triangle mesh.
|
/// A general triangle mesh.
|
||||||
|
@ -604,6 +611,8 @@ impl Rounding {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// How to paint some text on screen.
|
/// How to paint some text on screen.
|
||||||
|
///
|
||||||
|
/// This needs to be recreated if `pixels_per_point` (dpi scale) changes.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct TextShape {
|
pub struct TextShape {
|
||||||
|
|
|
@ -1334,6 +1334,11 @@ impl Tessellator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if galley.pixels_per_point != self.pixels_per_point {
|
||||||
|
eprintln!("epaint: WARNING: pixels_per_point (dpi scale) have changed between text layout and tessellation. \
|
||||||
|
You must recreate your text shapes if pixels_per_point changes.");
|
||||||
|
}
|
||||||
|
|
||||||
out.vertices.reserve(galley.num_vertices);
|
out.vertices.reserve(galley.num_vertices);
|
||||||
out.indices.reserve(galley.num_indices);
|
out.indices.reserve(galley.num_indices);
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ use crate::{
|
||||||
mutex::{Mutex, RwLock},
|
mutex::{Mutex, RwLock},
|
||||||
TextureAtlas,
|
TextureAtlas,
|
||||||
};
|
};
|
||||||
use ahash::AHashMap;
|
|
||||||
use emath::{vec2, Vec2};
|
use emath::{vec2, Vec2};
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -66,7 +65,7 @@ pub struct FontImpl {
|
||||||
// move each character by this much (hack)
|
// move each character by this much (hack)
|
||||||
y_offset: f32,
|
y_offset: f32,
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
glyph_info_cache: RwLock<AHashMap<char, GlyphInfo>>, // TODO(emilk): standard Mutex
|
glyph_info_cache: RwLock<ahash::HashMap<char, GlyphInfo>>, // TODO(emilk): standard Mutex
|
||||||
atlas: Arc<Mutex<TextureAtlas>>,
|
atlas: Arc<Mutex<TextureAtlas>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +220,7 @@ pub struct Font {
|
||||||
replacement_glyph: (FontIndex, GlyphInfo),
|
replacement_glyph: (FontIndex, GlyphInfo),
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
row_height: f32,
|
row_height: f32,
|
||||||
glyph_info_cache: AHashMap<char, (FontIndex, GlyphInfo)>,
|
glyph_info_cache: ahash::HashMap<char, (FontIndex, GlyphInfo)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Font {
|
impl Font {
|
||||||
|
|
|
@ -535,7 +535,7 @@ pub struct FontsImpl {
|
||||||
definitions: FontDefinitions,
|
definitions: FontDefinitions,
|
||||||
atlas: Arc<Mutex<TextureAtlas>>,
|
atlas: Arc<Mutex<TextureAtlas>>,
|
||||||
font_impl_cache: FontImplCache,
|
font_impl_cache: FontImplCache,
|
||||||
sized_family: ahash::AHashMap<(u32, FontFamily), Font>,
|
sized_family: ahash::HashMap<(u32, FontFamily), Font>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FontsImpl {
|
impl FontsImpl {
|
||||||
|
@ -673,7 +673,7 @@ struct FontImplCache {
|
||||||
ab_glyph_fonts: BTreeMap<String, (FontTweak, ab_glyph::FontArc)>,
|
ab_glyph_fonts: BTreeMap<String, (FontTweak, ab_glyph::FontArc)>,
|
||||||
|
|
||||||
/// Map font pixel sizes and names to the cached [`FontImpl`].
|
/// Map font pixel sizes and names to the cached [`FontImpl`].
|
||||||
cache: ahash::AHashMap<(u32, String), Arc<FontImpl>>,
|
cache: ahash::HashMap<(u32, String), Arc<FontImpl>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FontImplCache {
|
impl FontImplCache {
|
||||||
|
|
|
@ -478,6 +478,7 @@ fn galley_from_rows(point_scale: PointScale, job: Arc<LayoutJob>, mut rows: Vec<
|
||||||
mesh_bounds,
|
mesh_bounds,
|
||||||
num_vertices,
|
num_vertices,
|
||||||
num_indices,
|
num_indices,
|
||||||
|
pixels_per_point: point_scale.pixels_per_point,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -309,6 +309,8 @@ impl Default for TextWrapping {
|
||||||
/// Text that has been layed out, ready for painting.
|
/// Text that has been layed out, ready for painting.
|
||||||
///
|
///
|
||||||
/// You can create a [`Galley`] using [`crate::Fonts::layout_job`];
|
/// You can create a [`Galley`] using [`crate::Fonts::layout_job`];
|
||||||
|
///
|
||||||
|
/// This needs to be recreated if `pixels_per_point` (dpi scale) changes.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct Galley {
|
pub struct Galley {
|
||||||
|
@ -341,6 +343,12 @@ pub struct Galley {
|
||||||
|
|
||||||
/// Total number of indices in all the row meshes.
|
/// Total number of indices in all the row meshes.
|
||||||
pub num_indices: usize,
|
pub num_indices: usize,
|
||||||
|
|
||||||
|
/// The number of physical pixels for each logical point.
|
||||||
|
/// Since this affects the layout, we keep track of it
|
||||||
|
/// so that we can warn if this has changed once we get to
|
||||||
|
/// tessellation.
|
||||||
|
pub pixels_per_point: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::{ImageData, ImageDelta, TextureId};
|
use crate::{ImageData, ImageDelta, TextureId};
|
||||||
use ahash::AHashMap;
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -11,7 +10,7 @@ pub struct TextureManager {
|
||||||
/// We allocate texture id:s linearly.
|
/// We allocate texture id:s linearly.
|
||||||
next_id: u64,
|
next_id: u64,
|
||||||
/// Information about currently allocated textures.
|
/// Information about currently allocated textures.
|
||||||
metas: AHashMap<TextureId, TextureMeta>,
|
metas: ahash::HashMap<TextureId, TextureMeta>,
|
||||||
delta: TexturesDelta,
|
delta: TexturesDelta,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ pub use ordered_float::*;
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn hash(value: impl std::hash::Hash) -> u64 {
|
pub fn hash(value: impl std::hash::Hash) -> u64 {
|
||||||
use std::hash::Hasher as _;
|
use std::hash::Hasher as _;
|
||||||
let mut hasher = ahash::AHasher::new_with_keys(123, 456);
|
let mut hasher = ahash::AHasher::default();
|
||||||
value.hash(&mut hasher);
|
value.hash(&mut hasher);
|
||||||
hasher.finish()
|
hasher.finish()
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,16 @@ edition = "2021"
|
||||||
rust-version = "1.61"
|
rust-version = "1.61"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = { path = "../../eframe", features = ["glow"] }
|
eframe = { path = "../../eframe", features = ["glow"] }
|
||||||
egui_glow = { path = "../../egui_glow" }
|
egui_glow = { path = "../../egui_glow" }
|
||||||
glow = "0.11"
|
glow = "0.11"
|
||||||
three-d = { version = "0.12", default-features = false } # 2022-05-22
|
three-d = { version = "0.13", default-features = false }
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies] # Web dependencies
|
||||||
|
wasm-bindgen = "0.2" # Core bindings
|
||||||
|
wasm-bindgen-futures = "0.4" # Core bindings
|
||||||
|
console_error_panic_hook = "0.1" # For logging
|
||||||
|
|
|
@ -14,3 +14,7 @@ If you are content of having egui sit on top of a 3D background, take a look at:
|
||||||
```sh
|
```sh
|
||||||
cargo run -p custom_3d_three-d
|
cargo run -p custom_3d_three-d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
wasm-pack build examples/custom_3d_three-d --target web
|
||||||
|
```
|
||||||
|
|
38
examples/custom_3d_three-d/index.html
Normal file
38
examples/custom_3d_three-d/index.html
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="my" style="position: absolute;top:0;bottom: 0;left: 0;right: 0;margin:auto;"></canvas>
|
||||||
|
<!-- Note the usage of `type=module` here as this is an ES6 module -->
|
||||||
|
<script type="module">
|
||||||
|
// Use ES module import syntax to import functionality from the module
|
||||||
|
// that we have compiled.
|
||||||
|
//
|
||||||
|
// Note that the `default` import is an initialization function which
|
||||||
|
// will "boot" the module and make it ready to use. Currently browsers
|
||||||
|
// don't support natively imported WebAssembly as an ES module, but
|
||||||
|
// eventually the manual initialization won't be required!
|
||||||
|
import init from './pkg/custom_3d_three_d.js';
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
// First up we need to actually load the wasm file, so we use the
|
||||||
|
// default export to inform it where the wasm file is located on the
|
||||||
|
// server, and then we wait on the returned promise to wait for the
|
||||||
|
// wasm to be loaded.
|
||||||
|
// It may look like this: `await init('./pkg/without_a_bundler_bg.wasm');`,
|
||||||
|
// but there is also a handy default inside `init` function, which uses
|
||||||
|
// `import.meta` to locate the wasm file relatively to js file
|
||||||
|
//
|
||||||
|
// Note that instead of a string here you can also pass in an instance
|
||||||
|
// of `WebAssembly.Module` which allows you to compile your own module.
|
||||||
|
// Also note that the promise, when resolved, yields the wasm module's
|
||||||
|
// exports which is the same as importing the `*_bg` module in other
|
||||||
|
// modes
|
||||||
|
await init('./pkg/custom_3d_three_d_bg.wasm');
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
19
examples/custom_3d_three-d/src/lib.rs
Normal file
19
examples/custom_3d_three-d/src/lib.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
mod main;
|
||||||
|
|
||||||
|
// Entry point for wasm
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
#[wasm_bindgen(start)]
|
||||||
|
pub async fn start() -> Result<(), JsValue> {
|
||||||
|
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||||
|
|
||||||
|
let web_options = eframe::WebOptions::default();
|
||||||
|
eframe::start_web(
|
||||||
|
"my",
|
||||||
|
web_options,
|
||||||
|
Box::new(|cc| Box::new(main::MyApp::new(cc))),
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn main() {
|
fn main() {
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
initial_window_size: Some(egui::vec2(550.0, 610.0)),
|
initial_window_size: Some(egui::vec2(550.0, 610.0)),
|
||||||
|
@ -17,12 +19,12 @@ fn main() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MyApp {
|
pub struct MyApp {
|
||||||
angle: f32,
|
angle: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MyApp {
|
impl MyApp {
|
||||||
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||||
Self { angle: 0.2 }
|
Self { angle: 0.2 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +43,28 @@ impl eframe::App for MyApp {
|
||||||
|
|
||||||
egui::ScrollArea::both().show(ui, |ui| {
|
egui::ScrollArea::both().show(ui, |ui| {
|
||||||
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||||
self.custom_painting(ui);
|
let (rect, response) =
|
||||||
|
ui.allocate_exact_size(egui::Vec2::splat(512.0), egui::Sense::drag());
|
||||||
|
|
||||||
|
self.angle += response.drag_delta().x * 0.01;
|
||||||
|
|
||||||
|
// Clone locals so we can move them into the paint callback:
|
||||||
|
let angle = self.angle;
|
||||||
|
|
||||||
|
let callback = egui::PaintCallback {
|
||||||
|
rect,
|
||||||
|
callback: std::sync::Arc::new(egui_glow::CallbackFn::new(
|
||||||
|
move |info, painter| {
|
||||||
|
with_three_d(painter.gl(), |three_d| {
|
||||||
|
three_d.frame(
|
||||||
|
FrameInput::new(&three_d.context, &info, painter),
|
||||||
|
angle,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
ui.painter().add(callback);
|
||||||
});
|
});
|
||||||
ui.label("Drag to rotate!");
|
ui.label("Drag to rotate!");
|
||||||
});
|
});
|
||||||
|
@ -54,65 +77,68 @@ impl eframe::App for MyApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MyApp {
|
/// We get a [`glow::Context`] from `eframe` and we want to construct a [`ThreeDApp`].
|
||||||
fn custom_painting(&mut self, ui: &mut egui::Ui) {
|
|
||||||
let (rect, response) =
|
|
||||||
ui.allocate_exact_size(egui::Vec2::splat(512.0), egui::Sense::drag());
|
|
||||||
|
|
||||||
self.angle += response.drag_delta().x * 0.01;
|
|
||||||
|
|
||||||
// Clone locals so we can move them into the paint callback:
|
|
||||||
let angle = self.angle;
|
|
||||||
|
|
||||||
let callback = egui::PaintCallback {
|
|
||||||
rect,
|
|
||||||
callback: std::sync::Arc::new(egui_glow::CallbackFn::new(move |info, painter| {
|
|
||||||
with_three_d_context(painter.gl(), |three_d| {
|
|
||||||
paint_with_three_d(three_d, &info, angle);
|
|
||||||
});
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
ui.painter().add(callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// We get a [`glow::Context`] from `eframe`, but we want a [`three_d::Context`].
|
|
||||||
///
|
///
|
||||||
/// Sadly we can't just create a [`three_d::Context`] in [`MyApp::new`] and pass it
|
/// Sadly we can't just create a [`ThreeDApp`] in [`MyApp::new`] and pass it
|
||||||
/// to the [`egui::PaintCallback`] because [`three_d::Context`] isn't `Send+Sync`, which
|
/// to the [`egui::PaintCallback`] because [`glow::Context`] isn't `Send+Sync` on web, which
|
||||||
/// [`egui::PaintCallback`] is.
|
/// [`egui::PaintCallback`] needs. If you do not target web, then you can construct the [`ThreeDApp`] in [`MyApp::new`].
|
||||||
fn with_three_d_context<R>(
|
fn with_three_d<R>(gl: &std::sync::Arc<glow::Context>, f: impl FnOnce(&mut ThreeDApp) -> R) -> R {
|
||||||
gl: &std::sync::Arc<glow::Context>,
|
|
||||||
f: impl FnOnce(&three_d::Context) -> R,
|
|
||||||
) -> R {
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
thread_local! {
|
thread_local! {
|
||||||
pub static THREE_D: RefCell<Option<three_d::Context>> = RefCell::new(None);
|
pub static THREE_D: RefCell<Option<ThreeDApp>> = RefCell::new(None);
|
||||||
}
|
|
||||||
|
|
||||||
// If you are using the depth buffer you need to do this:
|
|
||||||
#[allow(unsafe_code)]
|
|
||||||
unsafe {
|
|
||||||
use glow::HasContext as _;
|
|
||||||
gl.enable(glow::DEPTH_TEST);
|
|
||||||
if !cfg!(target_arch = "wasm32") {
|
|
||||||
gl.disable(glow::FRAMEBUFFER_SRGB);
|
|
||||||
}
|
|
||||||
gl.clear(glow::DEPTH_BUFFER_BIT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
THREE_D.with(|three_d| {
|
THREE_D.with(|three_d| {
|
||||||
let mut three_d = three_d.borrow_mut();
|
let mut three_d = three_d.borrow_mut();
|
||||||
let three_d =
|
let three_d = three_d.get_or_insert_with(|| ThreeDApp::new(gl.clone()));
|
||||||
three_d.get_or_insert_with(|| three_d::Context::from_gl_context(gl.clone()).unwrap());
|
|
||||||
f(three_d)
|
f(three_d)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_with_three_d(three_d: &three_d::Context, info: &egui::PaintCallbackInfo, angle: f32) {
|
///
|
||||||
// Based on https://github.com/asny/three-d/blob/master/examples/triangle/src/main.rs
|
/// Translates from egui input to three-d input
|
||||||
|
///
|
||||||
|
pub struct FrameInput<'a> {
|
||||||
|
screen: three_d::RenderTarget<'a>,
|
||||||
|
viewport: three_d::Viewport,
|
||||||
|
scissor_box: three_d::ScissorBox,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameInput<'_> {
|
||||||
|
pub fn new(
|
||||||
|
context: &three_d::Context,
|
||||||
|
info: &egui::PaintCallbackInfo,
|
||||||
|
painter: &egui_glow::Painter,
|
||||||
|
) -> Self {
|
||||||
use three_d::*;
|
use three_d::*;
|
||||||
|
|
||||||
|
// Disable sRGB textures for three-d
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
unsafe {
|
||||||
|
use glow::HasContext as _;
|
||||||
|
context.disable(glow::FRAMEBUFFER_SRGB);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructs a screen render target to render the final image to
|
||||||
|
let screen = painter.intermediate_fbo().map_or_else(
|
||||||
|
|| {
|
||||||
|
RenderTarget::screen(
|
||||||
|
context,
|
||||||
|
info.viewport.width() as u32,
|
||||||
|
info.viewport.height() as u32,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|fbo| {
|
||||||
|
RenderTarget::from_framebuffer(
|
||||||
|
context,
|
||||||
|
info.viewport.width() as u32,
|
||||||
|
info.viewport.height() as u32,
|
||||||
|
fbo,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Set where to paint
|
// Set where to paint
|
||||||
let viewport = info.viewport_in_pixels();
|
let viewport = info.viewport_in_pixels();
|
||||||
let viewport = Viewport {
|
let viewport = Viewport {
|
||||||
|
@ -124,24 +150,44 @@ fn paint_with_three_d(three_d: &three_d::Context, info: &egui::PaintCallbackInfo
|
||||||
|
|
||||||
// Respect the egui clip region (e.g. if we are inside an `egui::ScrollArea`).
|
// Respect the egui clip region (e.g. if we are inside an `egui::ScrollArea`).
|
||||||
let clip_rect = info.clip_rect_in_pixels();
|
let clip_rect = info.clip_rect_in_pixels();
|
||||||
three_d.set_scissor(ScissorBox {
|
let scissor_box = ScissorBox {
|
||||||
x: clip_rect.left_px.round() as _,
|
x: clip_rect.left_px.round() as _,
|
||||||
y: clip_rect.from_bottom_px.round() as _,
|
y: clip_rect.from_bottom_px.round() as _,
|
||||||
width: clip_rect.width_px.round() as _,
|
width: clip_rect.width_px.round() as _,
|
||||||
height: clip_rect.height_px.round() as _,
|
height: clip_rect.height_px.round() as _,
|
||||||
});
|
};
|
||||||
|
Self {
|
||||||
let camera = Camera::new_perspective(
|
screen,
|
||||||
three_d,
|
scissor_box,
|
||||||
viewport,
|
viewport,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Based on the `three-d` [Triangle example](https://github.com/asny/three-d/blob/master/examples/triangle/src/main.rs).
|
||||||
|
/// This is where you'll need to customize
|
||||||
|
///
|
||||||
|
use three_d::*;
|
||||||
|
pub struct ThreeDApp {
|
||||||
|
context: Context,
|
||||||
|
camera: Camera,
|
||||||
|
model: Gm<Mesh, ColorMaterial>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreeDApp {
|
||||||
|
pub fn new(gl: std::sync::Arc<glow::Context>) -> Self {
|
||||||
|
let context = Context::from_gl_context(gl).unwrap();
|
||||||
|
// Create a camera
|
||||||
|
let camera = Camera::new_perspective(
|
||||||
|
Viewport::new_at_origo(1, 1),
|
||||||
vec3(0.0, 0.0, 2.0),
|
vec3(0.0, 0.0, 2.0),
|
||||||
vec3(0.0, 0.0, 0.0),
|
vec3(0.0, 0.0, 0.0),
|
||||||
vec3(0.0, 1.0, 0.0),
|
vec3(0.0, 1.0, 0.0),
|
||||||
degrees(45.0),
|
degrees(45.0),
|
||||||
0.1,
|
0.1,
|
||||||
10.0,
|
10.0,
|
||||||
)
|
);
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Create a CPU-side mesh consisting of a single colored triangle
|
// Create a CPU-side mesh consisting of a single colored triangle
|
||||||
let positions = vec![
|
let positions = vec![
|
||||||
|
@ -160,14 +206,31 @@ fn paint_with_three_d(three_d: &three_d::Context, info: &egui::PaintCallbackInfo
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut model = Gm::new(
|
// Construct a model, with a default color material, thereby transferring the mesh data to the GPU
|
||||||
Mesh::new(three_d, &cpu_mesh).unwrap(),
|
let model = Gm::new(Mesh::new(&context, &cpu_mesh), ColorMaterial::default());
|
||||||
ColorMaterial::default(),
|
Self {
|
||||||
);
|
context,
|
||||||
|
camera,
|
||||||
|
model,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn frame(&mut self, frame_input: FrameInput<'_>, angle: f32) -> Option<glow::Framebuffer> {
|
||||||
|
// Ensure the viewport matches the current window viewport which changes if the window is resized
|
||||||
|
self.camera.set_viewport(frame_input.viewport);
|
||||||
|
|
||||||
// Set the current transformation of the triangle
|
// Set the current transformation of the triangle
|
||||||
model.set_transformation(Mat4::from_angle_y(radians(angle)));
|
self.model
|
||||||
|
.set_transformation(Mat4::from_angle_y(radians(angle)));
|
||||||
|
|
||||||
|
// Get the screen render target to be able to render something on the screen
|
||||||
|
frame_input
|
||||||
|
.screen
|
||||||
|
// Clear the color and depth of the screen render target
|
||||||
|
.clear_partially(frame_input.scissor_box, ClearState::depth(1.0))
|
||||||
// Render the triangle with the color material which uses the per vertex colors defined at construction
|
// Render the triangle with the color material which uses the per vertex colors defined at construction
|
||||||
model.render(&camera, &[]).unwrap();
|
.render_partially(frame_input.scissor_box, &self.camera, &[&self.model], &[]);
|
||||||
|
|
||||||
|
frame_input.screen.into_framebuffer() // Take back the screen fbo, we will continue to use it.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,8 +99,8 @@ fn custon_window_frame(
|
||||||
rect
|
rect
|
||||||
};
|
};
|
||||||
let title_bar_response =
|
let title_bar_response =
|
||||||
ui.interact(title_bar_rect, Id::new("title_bar"), Sense::drag());
|
ui.interact(title_bar_rect, Id::new("title_bar"), Sense::click());
|
||||||
if title_bar_response.drag_started() {
|
if title_bar_response.is_pointer_button_down_on() {
|
||||||
frame.drag_window();
|
frame.drag_window();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
examples/serial_windows/Cargo.toml
Normal file
12
examples/serial_windows/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "serial_windows"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.61"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
eframe = { path = "../../eframe" }
|
8
examples/serial_windows/README.md
Normal file
8
examples/serial_windows/README.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Demonstrates how to open several windows after each other.
|
||||||
|
|
||||||
|
NOTE: this doesn't work on Mac due to <https://github.com/rust-windowing/winit/issues/2431>.
|
||||||
|
See also <https://github.com/emilk/egui/issues/1918>.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run -p serial_windows
|
||||||
|
```
|
53
examples/serial_windows/src/main.rs
Normal file
53
examples/serial_windows/src/main.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
|
use eframe::egui;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
eprintln!("WARNING: this example does not work on Mac! See https://github.com/emilk/egui/issues/1918");
|
||||||
|
}
|
||||||
|
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
run_and_return: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
eprintln!("Starting first window…");
|
||||||
|
eframe::run_native(
|
||||||
|
"First Window",
|
||||||
|
options.clone(),
|
||||||
|
Box::new(|_cc| Box::new(MyApp::default())),
|
||||||
|
);
|
||||||
|
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||||
|
|
||||||
|
eprintln!("Starting second window…");
|
||||||
|
eframe::run_native(
|
||||||
|
"Second Window",
|
||||||
|
options.clone(),
|
||||||
|
Box::new(|_cc| Box::new(MyApp::default())),
|
||||||
|
);
|
||||||
|
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||||
|
|
||||||
|
eprintln!("Starting third window…");
|
||||||
|
eframe::run_native(
|
||||||
|
"Third Window",
|
||||||
|
options,
|
||||||
|
Box::new(|_cc| Box::new(MyApp::default())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct MyApp {}
|
||||||
|
|
||||||
|
impl eframe::App for MyApp {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
if ui.button("Close").clicked() {
|
||||||
|
eprintln!("Pressed Close button");
|
||||||
|
frame.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue