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:
|
||||
profile: minimal
|
||||
toolchain: 1.61.0
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
- run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
|
@ -87,8 +87,8 @@ jobs:
|
|||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.61.0
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
- run: rustup target add wasm32-unknown-unknown
|
||||
- name: check
|
||||
run: cargo check -p eframe --lib --no-default-features --features glow,persistence --target wasm32-unknown-unknown
|
||||
|
||||
|
@ -101,8 +101,8 @@ jobs:
|
|||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.61.0
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
- run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
|
@ -136,7 +136,7 @@ jobs:
|
|||
profile: minimal
|
||||
toolchain: 1.61.0
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
components: rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
|
@ -191,7 +191,22 @@ jobs:
|
|||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.61.0
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
- run: rustup target add wasm32-unknown-unknown
|
||||
- run: ./sh/setup_web.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
|
||||
/.vscode
|
||||
/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)).
|
||||
* `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 `PointerState::button_double_clicked()` and `PointerState::button_triple_clicked()` ([#1906](https://github.com/emilk/egui/issues/1906)).
|
||||
|
||||
### Changed
|
||||
* 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",
|
||||
"epaint",
|
||||
|
||||
"examples/confirm_exit",
|
||||
"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",
|
||||
"examples/*",
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
|
|
|
@ -58,6 +58,7 @@ allow = [
|
|||
"MIT", # https://tldrlegal.com/license/mit-license
|
||||
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11
|
||||
"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)
|
||||
]
|
||||
|
||||
|
|
|
@ -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-wgpu = { version = "0.18.0", path = "../egui-wgpu", optional = true, features = ["winit"] }
|
||||
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"] }
|
||||
wgpu = { version = "0.13", optional = true }
|
||||
|
||||
|
@ -83,8 +83,8 @@ wgpu = { version = "0.13", optional = true }
|
|||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
dark-light = { version = "0.2.1", optional = true }
|
||||
egui-winit = { version = "0.18.0", path = "../egui-winit", default-features = false, features = ["clipboard", "links"] }
|
||||
glutin = { version = "0.28.0" }
|
||||
winit = "0.26.1"
|
||||
glutin = { version = "0.29.0" }
|
||||
winit = "0.27.2"
|
||||
|
||||
# optional native:
|
||||
puffin = { version = "0.13", optional = true }
|
||||
|
@ -94,6 +94,7 @@ directories-next = { version = "2", optional = true }
|
|||
# web:
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
bytemuck = "1.7"
|
||||
getrandom = { version = "0.2", features = ["js"] } # used by ahash
|
||||
js-sys = "0.3"
|
||||
percent-encoding = "2.1"
|
||||
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
|
||||
/// 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")]
|
||||
pub gl: Option<std::sync::Arc<glow::Context>>,
|
||||
|
||||
/// Can be used to manage GPU resources for custom rendering with WGPU using
|
||||
/// [`egui::PaintCallback`]s.
|
||||
/// 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 render_state: Option<egui_wgpu::RenderState>,
|
||||
pub wgpu_render_state: Option<egui_wgpu::RenderState>,
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -281,6 +286,20 @@ pub struct NativeOptions {
|
|||
///
|
||||
/// Default: `Theme::Dark`.
|
||||
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"))]
|
||||
|
@ -307,6 +326,7 @@ impl Default for NativeOptions {
|
|||
renderer: Renderer::default(),
|
||||
follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"),
|
||||
default_theme: Theme::Dark,
|
||||
run_and_return: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -507,10 +527,9 @@ pub struct Frame {
|
|||
#[cfg(feature = "glow")]
|
||||
pub(crate) gl: Option<std::sync::Arc<glow::Context>>,
|
||||
|
||||
/// Can be used to manage GPU resources for custom rendering with WGPU using
|
||||
/// [`egui::PaintCallback`]s.
|
||||
/// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s.
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub render_state: Option<egui_wgpu::RenderState>,
|
||||
pub(crate) wgpu_render_state: Option<egui_wgpu::RenderState>,
|
||||
}
|
||||
|
||||
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).
|
||||
///
|
||||
/// 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")]
|
||||
pub fn gl(&self) -> Option<&std::sync::Arc<glow::Context>> {
|
||||
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).
|
||||
/// The framework will not quit immediately, but at the end of the this frame.
|
||||
#[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.
|
||||
#[cfg(feature = "ron")]
|
||||
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
|
||||
|
|
|
@ -161,20 +161,20 @@ mod native;
|
|||
/// ```
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[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;
|
||||
|
||||
match renderer {
|
||||
#[cfg(feature = "glow")]
|
||||
Renderer::Glow => {
|
||||
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")]
|
||||
Renderer::Wgpu => {
|
||||
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>,
|
||||
storage: Option<Box<dyn epi::Storage>>,
|
||||
#[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 {
|
||||
let egui_ctx = egui::Context::default();
|
||||
|
||||
|
@ -214,7 +214,7 @@ impl EpiIntegration {
|
|||
#[cfg(feature = "glow")]
|
||||
gl,
|
||||
#[cfg(feature = "wgpu")]
|
||||
render_state,
|
||||
wgpu_render_state,
|
||||
};
|
||||
|
||||
let mut egui_winit = egui_winit::State::new(event_loop);
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
use super::epi_integration;
|
||||
use crate::epi;
|
||||
use egui_winit::winit;
|
||||
//! Note that this file contains two similar paths - one for [`glow`], one for [`wgpu`].
|
||||
//! When making changes to one you often also want to apply it to the other.
|
||||
|
||||
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;
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
|
@ -9,7 +18,7 @@ struct RequestRepaintEvent;
|
|||
fn create_display(
|
||||
native_options: &NativeOptions,
|
||||
window_builder: winit::window::WindowBuilder,
|
||||
event_loop: &winit::event_loop::EventLoop<RequestRepaintEvent>,
|
||||
event_loop: &EventLoop<RequestRepaintEvent>,
|
||||
) -> (
|
||||
glutin::WindowedContext<glutin::PossiblyCurrent>,
|
||||
glow::Context,
|
||||
|
@ -47,28 +56,210 @@ fn create_display(
|
|||
|
||||
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
|
||||
#[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,
|
||||
native_options: &epi::NativeOptions,
|
||||
app_creator: epi::AppCreator,
|
||||
) -> ! {
|
||||
) -> Self {
|
||||
let storage = epi_integration::create_storage(app_name);
|
||||
let window_settings = epi_integration::load_window_settings(storage.as_deref());
|
||||
let event_loop = winit::event_loop::EventLoop::with_user_event();
|
||||
|
||||
let window_builder =
|
||||
epi_integration::window_builder(native_options, &window_settings).with_title(app_name);
|
||||
let (gl_window, gl) = create_display(native_options, window_builder, &event_loop);
|
||||
let gl = std::sync::Arc::new(gl);
|
||||
let window_builder = epi_integration::window_builder(native_options, &window_settings)
|
||||
.with_title(app_name);
|
||||
let (gl_window, gl) = create_display(native_options, window_builder, event_loop);
|
||||
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));
|
||||
|
||||
let system_theme = native_options.system_theme();
|
||||
let mut integration = epi_integration::EpiIntegration::new(
|
||||
&event_loop,
|
||||
event_loop,
|
||||
painter.max_texture_side(),
|
||||
gl_window.window(),
|
||||
system_theme,
|
||||
|
@ -93,27 +284,63 @@ pub fn run_glow(
|
|||
storage: integration.frame.storage(),
|
||||
gl: Some(gl.clone()),
|
||||
#[cfg(feature = "wgpu")]
|
||||
render_state: None,
|
||||
wgpu_render_state: None,
|
||||
});
|
||||
|
||||
if app.warm_up_enabled() {
|
||||
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| {
|
||||
let window = gl_window.window();
|
||||
impl WinitApp for GlowWinitApp {
|
||||
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")]
|
||||
puffin::GlobalProfiler::lock().new_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();
|
||||
|
||||
egui_glow::painter::clear(
|
||||
&gl,
|
||||
gl,
|
||||
screen_size_in_pixels,
|
||||
app.clear_color(&integration.egui_ctx.style().visuals),
|
||||
);
|
||||
|
@ -146,11 +373,10 @@ pub fn run_glow(
|
|||
gl_window.swap_buffers().unwrap();
|
||||
}
|
||||
|
||||
*control_flow = if integration.should_quit() {
|
||||
winit::event_loop::ControlFlow::Exit
|
||||
let control_flow = if integration.should_quit() {
|
||||
EventResult::Exit
|
||||
} else if repaint_after.is_zero() {
|
||||
window.request_redraw();
|
||||
winit::event_loop::ControlFlow::Poll
|
||||
EventResult::RepaintAsap
|
||||
} else if let Some(repaint_after_instant) =
|
||||
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*
|
||||
// winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own
|
||||
// egui backend impl i guess.
|
||||
winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant)
|
||||
EventResult::RepaintAt(repaint_after_instant)
|
||||
} else {
|
||||
winit::event_loop::ControlFlow::Wait
|
||||
EventResult::Wait
|
||||
};
|
||||
|
||||
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
|
||||
// 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.
|
||||
|
@ -175,78 +401,106 @@ pub fn run_glow(
|
|||
crate::profile_scope!("bg_sleep");
|
||||
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, .. } => {
|
||||
match &event {
|
||||
winit::event::WindowEvent::Focused(new_focused) => {
|
||||
is_focused = *new_focused;
|
||||
self.is_focused = *new_focused;
|
||||
}
|
||||
winit::event::WindowEvent::Resized(physical_size) => {
|
||||
// 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
|
||||
// This solves an issue where the app would panic when minimizing on Windows.
|
||||
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, .. } => {
|
||||
gl_window.resize(**new_inner_size);
|
||||
winit::event::WindowEvent::ScaleFactorChanged {
|
||||
new_inner_size, ..
|
||||
} => {
|
||||
self.gl_window.resize(**new_inner_size);
|
||||
}
|
||||
winit::event::WindowEvent::CloseRequested if integration.should_quit() => {
|
||||
*control_flow = winit::event_loop::ControlFlow::Exit;
|
||||
winit::event::WindowEvent::CloseRequested
|
||||
if self.integration.should_quit() =>
|
||||
{
|
||||
return EventResult::Exit
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
integration.on_event(app.as_mut(), &event);
|
||||
if integration.should_quit() {
|
||||
*control_flow = winit::event_loop::ControlFlow::Exit;
|
||||
self.integration.on_event(self.app.as_mut(), &event);
|
||||
|
||||
if self.integration.should_quit() {
|
||||
EventResult::Exit
|
||||
} else {
|
||||
// TODO(emilk): ask egui if the event warrants a repaint
|
||||
EventResult::RepaintAsap
|
||||
}
|
||||
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();
|
||||
_ => EventResult::Wait,
|
||||
}
|
||||
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
|
||||
/// Run an egui app
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub fn run_wgpu(
|
||||
pub fn run_glow(
|
||||
app_name: &str,
|
||||
native_options: &epi::NativeOptions,
|
||||
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 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)
|
||||
.with_title(app_name)
|
||||
.build(&event_loop)
|
||||
.build(event_loop)
|
||||
.unwrap();
|
||||
|
||||
// SAFETY: `window` must outlive `painter`.
|
||||
#[allow(unsafe_code)]
|
||||
let mut painter = unsafe {
|
||||
#[allow(unsafe_code, unused_mut, unused_unsafe)]
|
||||
let painter = unsafe {
|
||||
let mut painter = egui_wgpu::winit::Painter::new(
|
||||
wgpu::Backends::PRIMARY | wgpu::Backends::GL,
|
||||
wgpu::PowerPreference::HighPerformance,
|
||||
|
@ -263,18 +517,18 @@ pub fn run_wgpu(
|
|||
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 mut integration = epi_integration::EpiIntegration::new(
|
||||
&event_loop,
|
||||
event_loop,
|
||||
painter.max_texture_side().unwrap_or(2048),
|
||||
&window,
|
||||
system_theme,
|
||||
storage,
|
||||
#[cfg(feature = "glow")]
|
||||
None,
|
||||
Some(render_state.clone()),
|
||||
wgpu_render_state.clone(),
|
||||
);
|
||||
let theme = system_theme.unwrap_or(native_options.default_theme);
|
||||
integration.egui_ctx.set_visuals(theme.egui_visuals());
|
||||
|
@ -292,23 +546,61 @@ pub fn run_wgpu(
|
|||
storage: integration.frame.storage(),
|
||||
#[cfg(feature = "glow")]
|
||||
gl: None,
|
||||
render_state: Some(render_state),
|
||||
wgpu_render_state,
|
||||
});
|
||||
|
||||
if app.warm_up_enabled() {
|
||||
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| {
|
||||
let window = &window;
|
||||
impl WinitApp for WgpuWinitApp {
|
||||
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")]
|
||||
puffin::GlobalProfiler::lock().new_frame();
|
||||
crate::profile_scope!("frame");
|
||||
|
||||
let Self {
|
||||
window,
|
||||
app,
|
||||
integration,
|
||||
painter,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let egui::FullOutput {
|
||||
platform_output,
|
||||
repaint_after,
|
||||
|
@ -330,11 +622,12 @@ pub fn run_wgpu(
|
|||
&textures_delta,
|
||||
);
|
||||
|
||||
*control_flow = if integration.should_quit() {
|
||||
winit::event_loop::ControlFlow::Exit
|
||||
integration.post_rendering(app.as_mut(), window);
|
||||
|
||||
let control_flow = if integration.should_quit() {
|
||||
EventResult::Exit
|
||||
} else if repaint_after.is_zero() {
|
||||
window.request_redraw();
|
||||
winit::event_loop::ControlFlow::Poll
|
||||
EventResult::RepaintAsap
|
||||
} else if let Some(repaint_after_instant) =
|
||||
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*
|
||||
// winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own
|
||||
// egui backend impl i guess.
|
||||
winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant)
|
||||
EventResult::RepaintAt(repaint_after_instant)
|
||||
} else {
|
||||
winit::event_loop::ControlFlow::Wait
|
||||
EventResult::Wait
|
||||
};
|
||||
|
||||
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
|
||||
// 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.
|
||||
|
@ -359,70 +652,84 @@ pub fn run_wgpu(
|
|||
crate::profile_scope!("bg_sleep");
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
};
|
||||
|
||||
control_flow
|
||||
}
|
||||
|
||||
fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult {
|
||||
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")]
|
||||
winit::event::Event::Resumed => unsafe {
|
||||
painter.set_window(Some(&window));
|
||||
self.painter.set_window(Some(&self.window));
|
||||
EventResult::RepaintAsap
|
||||
},
|
||||
#[cfg(target_os = "android")]
|
||||
winit::event::Event::Paused => unsafe {
|
||||
painter.set_window(None);
|
||||
winit::event::Event::Suspended => unsafe {
|
||||
self.painter.set_window(None);
|
||||
EventResult::Wait
|
||||
},
|
||||
|
||||
winit::event::Event::WindowEvent { event, .. } => {
|
||||
match &event {
|
||||
winit::event::WindowEvent::Focused(new_focused) => {
|
||||
is_focused = *new_focused;
|
||||
self.is_focused = *new_focused;
|
||||
}
|
||||
winit::event::WindowEvent::Resized(physical_size) => {
|
||||
// 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
|
||||
// This solves an issue where the app would panic when minimizing on Windows.
|
||||
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, .. } => {
|
||||
painter.on_window_resized(new_inner_size.width, new_inner_size.height);
|
||||
winit::event::WindowEvent::ScaleFactorChanged {
|
||||
new_inner_size, ..
|
||||
} => {
|
||||
self.painter
|
||||
.on_window_resized(new_inner_size.width, new_inner_size.height);
|
||||
}
|
||||
winit::event::WindowEvent::CloseRequested if integration.should_quit() => {
|
||||
*control_flow = winit::event_loop::ControlFlow::Exit;
|
||||
winit::event::WindowEvent::CloseRequested
|
||||
if self.integration.should_quit() =>
|
||||
{
|
||||
return EventResult::Exit
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
integration.on_event(app.as_mut(), &event);
|
||||
if integration.should_quit() {
|
||||
*control_flow = winit::event_loop::ControlFlow::Exit;
|
||||
self.integration.on_event(self.app.as_mut(), &event);
|
||||
if self.integration.should_quit() {
|
||||
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")]
|
||||
app.on_exit(None);
|
||||
|
||||
#[cfg(not(feature = "glow"))]
|
||||
app.on_exit();
|
||||
|
||||
painter.destroy();
|
||||
}
|
||||
winit::event::Event::UserEvent(RequestRepaintEvent) => window.request_redraw(),
|
||||
winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
||||
..
|
||||
}) => {
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
pub fn run_wgpu(
|
||||
app_name: &str,
|
||||
native_options: &epi::NativeOptions,
|
||||
app_creator: epi::AppCreator,
|
||||
) {
|
||||
if native_options.run_and_return {
|
||||
with_event_loop(|event_loop| {
|
||||
let wgpu_eframe =
|
||||
WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
|
||||
run_and_return(event_loop, wgpu_eframe);
|
||||
});
|
||||
} 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")]
|
||||
gl: Some(painter.painter.gl().clone()),
|
||||
#[cfg(feature = "wgpu")]
|
||||
render_state: None,
|
||||
wgpu_render_state: None,
|
||||
});
|
||||
|
||||
let frame = epi::Frame {
|
||||
|
@ -229,7 +229,7 @@ impl AppRunner {
|
|||
#[cfg(feature = "glow")]
|
||||
gl: Some(painter.gl().clone()),
|
||||
#[cfg(feature = "wgpu")]
|
||||
render_state: None,
|
||||
wgpu_render_state: None,
|
||||
};
|
||||
|
||||
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
|
||||
|
|
|
@ -47,4 +47,4 @@ wgpu = "0.13"
|
|||
document-features = { 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
|
||||
/// [`egui_extras::RetainedImage`](https://docs.rs/egui_extras/latest/egui_extras/image/struct.RetainedImage.html)
|
||||
/// or [`egui::Context::load_texture`].
|
||||
pub fn get_texture(
|
||||
pub fn texture(
|
||||
&self,
|
||||
id: &egui::TextureId,
|
||||
) -> Option<&(Option<wgpu::Texture>, wgpu::BindGroup)> {
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::sync::Arc;
|
|||
|
||||
use egui::mutex::RwLock;
|
||||
use tracing::error;
|
||||
use wgpu::{Adapter, Instance, Surface, TextureFormat};
|
||||
use wgpu::{Adapter, Instance, Surface};
|
||||
|
||||
use crate::renderer;
|
||||
|
||||
|
@ -12,7 +12,7 @@ use crate::renderer;
|
|||
pub struct RenderState {
|
||||
pub device: Arc<wgpu::Device>,
|
||||
pub queue: Arc<wgpu::Queue>,
|
||||
pub target_format: TextureFormat,
|
||||
pub target_format: wgpu::TextureFormat,
|
||||
pub egui_rpass: Arc<RwLock<renderer::RenderPass>>,
|
||||
}
|
||||
|
||||
|
@ -75,14 +75,14 @@ impl<'a> Painter<'a> {
|
|||
/// Get the [`RenderState`].
|
||||
///
|
||||
/// 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()
|
||||
}
|
||||
|
||||
async fn init_render_state(
|
||||
&self,
|
||||
adapter: &Adapter,
|
||||
target_format: TextureFormat,
|
||||
target_format: wgpu::TextureFormat,
|
||||
) -> RenderState {
|
||||
let (device, queue) =
|
||||
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
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
winit = "0.26.1"
|
||||
winit = "0.27.2"
|
||||
|
||||
#! ### Optional dependencies
|
||||
arboard = { version = "2.1", optional = true, default-features = false }
|
||||
|
||||
## Enable this when generating docs.
|
||||
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]
|
||||
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,
|
||||
/// then a fallback clipboard that just works works within the same app is used instead.
|
||||
pub struct Clipboard {
|
||||
#[cfg(feature = "arboard")]
|
||||
#[cfg(all(feature = "arboard", not(target_os = "android")))]
|
||||
arboard: Option<arboard::Clipboard>,
|
||||
|
||||
#[cfg(all(
|
||||
|
@ -28,7 +28,7 @@ impl Clipboard {
|
|||
#[allow(unused_variables)]
|
||||
pub fn new(#[allow(unused_variables)] wayland_display: Option<*mut c_void>) -> Self {
|
||||
Self {
|
||||
#[cfg(feature = "arboard")]
|
||||
#[cfg(all(feature = "arboard", not(target_os = "android")))]
|
||||
arboard: init_arboard(),
|
||||
#[cfg(all(
|
||||
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 {
|
||||
return match clipboard.get_text() {
|
||||
Ok(text) => Some(text),
|
||||
|
@ -96,7 +96,7 @@ impl Clipboard {
|
|||
return;
|
||||
}
|
||||
|
||||
#[cfg(feature = "arboard")]
|
||||
#[cfg(all(feature = "arboard", not(target_os = "android")))]
|
||||
if let Some(clipboard) = &mut self.arboard {
|
||||
if let Err(err) = clipboard.set_text(text) {
|
||||
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> {
|
||||
match arboard::Clipboard::new() {
|
||||
Ok(clipboard) => Some(clipboard),
|
||||
|
|
|
@ -66,7 +66,7 @@ pub struct State {
|
|||
|
||||
impl State {
|
||||
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 {
|
||||
|
@ -400,7 +400,7 @@ impl State {
|
|||
}
|
||||
|
||||
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) => {
|
||||
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
|
||||
|
@ -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 {
|
||||
// Treat as zoom instead:
|
||||
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
|
||||
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(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
pub struct WindowSettings {
|
||||
/// Position of window in physical pixels. This is either
|
||||
/// 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>,
|
||||
|
||||
fullscreen: bool,
|
||||
|
|
|
@ -57,14 +57,14 @@ serde = ["dep:serde", "epaint/serde"]
|
|||
[dependencies]
|
||||
epaint = { version = "0.18.1", path = "../epaint", default-features = false }
|
||||
|
||||
ahash = "0.7"
|
||||
ahash = "0.8"
|
||||
nohash-hasher = "0.2"
|
||||
|
||||
#! ### Optional dependencies
|
||||
## Enable this when generating docs.
|
||||
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"] }
|
||||
|
||||
# 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).
|
||||
pub fn new(source: impl std::hash::Hash) -> Id {
|
||||
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);
|
||||
Id(hasher.finish())
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ impl Id {
|
|||
/// Generate a new [`Id`] by hashing the parent [`Id`] and the given argument.
|
||||
pub fn with(self, child: impl std::hash::Hash) -> Id {
|
||||
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);
|
||||
child.hash(&mut hasher);
|
||||
Id(hasher.finish())
|
||||
|
|
|
@ -750,6 +750,20 @@ impl PointerState {
|
|||
.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?
|
||||
pub fn primary_clicked(&self) -> bool {
|
||||
self.button_clicked(PointerButton::Primary)
|
||||
|
|
|
@ -252,7 +252,13 @@ impl Layout {
|
|||
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.
|
||||
/// 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};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -490,15 +488,15 @@ pub struct Areas {
|
|||
areas: IdMap<area::State>,
|
||||
/// Back-to-front. Top is last.
|
||||
order: Vec<LayerId>,
|
||||
visible_last_frame: AHashSet<LayerId>,
|
||||
visible_current_frame: AHashSet<LayerId>,
|
||||
visible_last_frame: ahash::HashSet<LayerId>,
|
||||
visible_current_frame: ahash::HashSet<LayerId>,
|
||||
|
||||
/// 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.
|
||||
/// 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,
|
||||
/// 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 {
|
||||
|
@ -550,7 +548,7 @@ impl Areas {
|
|||
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
|
||||
.iter()
|
||||
.cloned()
|
||||
|
|
|
@ -532,7 +532,7 @@ impl MenuState {
|
|||
id: Id,
|
||||
add_contents: impl FnOnce(&mut Ui) -> 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);
|
||||
(sub.read().response, inner_response.inner)
|
||||
})?;
|
||||
|
@ -582,7 +582,7 @@ impl MenuState {
|
|||
if pointer.is_still() {
|
||||
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() {
|
||||
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.
|
||||
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() {
|
||||
return sub_menu.read().area_contains(pos);
|
||||
}
|
||||
|
@ -608,18 +608,18 @@ impl MenuState {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fn get_submenu(&mut self, id: Id) -> Option<&Arc<RwLock<MenuState>>> {
|
||||
fn submenu(&mut self, id: Id) -> Option<&Arc<RwLock<MenuState>>> {
|
||||
self.sub_menu
|
||||
.as_ref()
|
||||
.and_then(|(k, sub)| if id == *k { Some(sub) } else { None })
|
||||
|
|
|
@ -99,7 +99,7 @@ impl Ui {
|
|||
crate::egui_assert!(!max_rect.any_nan());
|
||||
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);
|
||||
let menu_state = self.get_menu_state();
|
||||
let menu_state = self.menu_state();
|
||||
Ui {
|
||||
id: self.id.with(id_source),
|
||||
next_auto_id_source,
|
||||
|
@ -2096,7 +2096,7 @@ impl Ui {
|
|||
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()
|
||||
}
|
||||
|
||||
|
|
|
@ -119,7 +119,7 @@ impl<Value: 'static + Send + Sync, Computer: 'static + Send + Sync> CacheTrait
|
|||
/// ```
|
||||
#[derive(Default)]
|
||||
pub struct CacheStorage {
|
||||
caches: ahash::AHashMap<std::any::TypeId, Box<dyn CacheTrait>>,
|
||||
caches: ahash::HashMap<std::any::TypeId, Box<dyn CacheTrait>>,
|
||||
}
|
||||
|
||||
impl CacheStorage {
|
||||
|
|
|
@ -32,7 +32,7 @@ pub(super) struct PlotConfig<'a> {
|
|||
|
||||
/// Trait shared by things that can be drawn in the plot.
|
||||
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>);
|
||||
|
||||
|
@ -46,7 +46,7 @@ pub(super) trait PlotItem {
|
|||
|
||||
fn geometry(&self) -> PlotGeometry<'_>;
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds;
|
||||
fn bounds(&self) -> PlotBounds;
|
||||
|
||||
fn find_closest(&self, point: Pos2, transform: &ScreenTransform) -> Option<ClosestElem> {
|
||||
match self.geometry() {
|
||||
|
@ -167,7 +167,7 @@ impl 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 {
|
||||
y,
|
||||
stroke,
|
||||
|
@ -204,7 +204,7 @@ impl PlotItem for HLine {
|
|||
PlotGeometry::None
|
||||
}
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds {
|
||||
fn bounds(&self) -> PlotBounds {
|
||||
let mut bounds = PlotBounds::NOTHING;
|
||||
bounds.min[1] = self.y;
|
||||
bounds.max[1] = self.y;
|
||||
|
@ -277,7 +277,7 @@ impl 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 {
|
||||
x,
|
||||
stroke,
|
||||
|
@ -314,7 +314,7 @@ impl PlotItem for VLine {
|
|||
PlotGeometry::None
|
||||
}
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds {
|
||||
fn bounds(&self) -> PlotBounds {
|
||||
let mut bounds = PlotBounds::NOTHING;
|
||||
bounds.min[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 {
|
||||
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 {
|
||||
series,
|
||||
stroke,
|
||||
|
@ -484,8 +484,8 @@ impl PlotItem for Line {
|
|||
PlotGeometry::Points(self.series.points())
|
||||
}
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds {
|
||||
self.series.get_bounds()
|
||||
fn bounds(&self) -> PlotBounds {
|
||||
self.series.bounds()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -562,7 +562,7 @@ impl 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 {
|
||||
series,
|
||||
stroke,
|
||||
|
@ -614,8 +614,8 @@ impl PlotItem for Polygon {
|
|||
PlotGeometry::Points(self.series.points())
|
||||
}
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds {
|
||||
self.series.get_bounds()
|
||||
fn bounds(&self) -> PlotBounds {
|
||||
self.series.bounds()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -674,7 +674,7 @@ impl 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 {
|
||||
ui.style().visuals.text_color()
|
||||
} else {
|
||||
|
@ -728,7 +728,7 @@ impl PlotItem for Text {
|
|||
PlotGeometry::None
|
||||
}
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds {
|
||||
fn bounds(&self) -> PlotBounds {
|
||||
let mut bounds = PlotBounds::NOTHING;
|
||||
bounds.extend_with(&self.position);
|
||||
bounds
|
||||
|
@ -814,7 +814,7 @@ impl 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 frac_sqrt_3_2 = 3_f32.sqrt() / 2.0;
|
||||
let frac_1_sqrt_2 = 1.0 / 2_f32.sqrt();
|
||||
|
@ -965,8 +965,8 @@ impl PlotItem for Points {
|
|||
PlotGeometry::Points(self.series.points())
|
||||
}
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds {
|
||||
self.series.get_bounds()
|
||||
fn bounds(&self) -> PlotBounds {
|
||||
self.series.bounds()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1016,7 +1016,7 @@ impl 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::*;
|
||||
let Self {
|
||||
origins,
|
||||
|
@ -1080,8 +1080,8 @@ impl PlotItem for Arrows {
|
|||
PlotGeometry::Points(self.origins.points())
|
||||
}
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds {
|
||||
self.origins.get_bounds()
|
||||
fn bounds(&self) -> PlotBounds {
|
||||
self.origins.bounds()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1155,7 +1155,7 @@ impl 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 {
|
||||
position,
|
||||
texture_id,
|
||||
|
@ -1215,7 +1215,7 @@ impl PlotItem for PlotImage {
|
|||
PlotGeometry::None
|
||||
}
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds {
|
||||
fn bounds(&self) -> PlotBounds {
|
||||
let mut bounds = PlotBounds::NOTHING;
|
||||
let left_top = PlotPoint::new(
|
||||
self.position.x as f32 - self.size.x / 2.0,
|
||||
|
@ -1346,7 +1346,7 @@ impl 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 {
|
||||
b.add_shapes(transform, self.highlight, shapes);
|
||||
}
|
||||
|
@ -1376,7 +1376,7 @@ impl PlotItem for BarChart {
|
|||
PlotGeometry::Rects
|
||||
}
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds {
|
||||
fn bounds(&self) -> PlotBounds {
|
||||
let mut bounds = PlotBounds::NOTHING;
|
||||
for b in &self.bars {
|
||||
bounds.merge(&b.bounds());
|
||||
|
@ -1488,7 +1488,7 @@ impl 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 {
|
||||
b.add_shapes(transform, self.highlight, shapes);
|
||||
}
|
||||
|
@ -1518,7 +1518,7 @@ impl PlotItem for BoxPlot {
|
|||
PlotGeometry::Rects
|
||||
}
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds {
|
||||
fn bounds(&self) -> PlotBounds {
|
||||
let mut bounds = PlotBounds::NOTHING;
|
||||
for b in &self.boxes {
|
||||
bounds.merge(&b.bounds());
|
||||
|
|
|
@ -302,7 +302,7 @@ impl PlotPoints {
|
|||
(start < end).then(|| start..=end)
|
||||
}
|
||||
|
||||
pub(super) fn get_bounds(&self) -> PlotBounds {
|
||||
pub(super) fn bounds(&self) -> PlotBounds {
|
||||
match self {
|
||||
PlotPoints::Owned(points) => {
|
||||
let mut bounds = PlotBounds::NOTHING;
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
use std::{collections::BTreeMap, string::String};
|
||||
|
||||
use epaint::ahash::AHashSet;
|
||||
|
||||
use crate::*;
|
||||
|
||||
use super::items::PlotItem;
|
||||
|
@ -168,7 +166,7 @@ impl LegendWidget {
|
|||
rect: Rect,
|
||||
config: Legend,
|
||||
items: &[Box<dyn PlotItem>],
|
||||
hidden_items: &AHashSet<String>,
|
||||
hidden_items: &ahash::HashSet<String>,
|
||||
) -> Option<Self> {
|
||||
// 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.
|
||||
|
@ -199,7 +197,7 @@ impl LegendWidget {
|
|||
}
|
||||
|
||||
// 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
|
||||
.iter()
|
||||
.filter(|(_, entry)| !entry.checked)
|
||||
|
@ -208,7 +206,7 @@ impl LegendWidget {
|
|||
}
|
||||
|
||||
// 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
|
||||
.iter()
|
||||
.find(|(_, entry)| entry.hovered)
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
use std::{cell::Cell, ops::RangeInclusive, rc::Rc};
|
||||
|
||||
use crate::*;
|
||||
use epaint::ahash::AHashSet;
|
||||
use epaint::color::Hsva;
|
||||
use epaint::util::FloatOrd;
|
||||
|
||||
|
@ -96,7 +95,7 @@ impl From<bool> for AutoBounds {
|
|||
struct PlotMemory {
|
||||
auto_bounds: AutoBounds,
|
||||
hovered_entry: Option<String>,
|
||||
hidden_items: AHashSet<String>,
|
||||
hidden_items: ahash::HashSet<String>,
|
||||
min_auto_bounds: PlotBounds,
|
||||
last_screen_transform: ScreenTransform,
|
||||
/// Allows to remember the first click position when performing a boxed zoom
|
||||
|
@ -430,7 +429,7 @@ impl Plot {
|
|||
///
|
||||
/// The function has this signature:
|
||||
/// ```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.
|
||||
|
@ -697,7 +696,7 @@ impl Plot {
|
|||
}
|
||||
|
||||
for item in &items {
|
||||
let item_bounds = item.get_bounds();
|
||||
let item_bounds = item.bounds();
|
||||
|
||||
if auto_bounds.x {
|
||||
bounds.merge_x(&item_bounds);
|
||||
|
@ -829,8 +828,8 @@ impl Plot {
|
|||
|
||||
if let Some(mut legend) = legend {
|
||||
ui.add(&mut legend);
|
||||
hidden_items = legend.get_hidden_items();
|
||||
hovered_entry = legend.get_hovered_entry_name();
|
||||
hidden_items = legend.hidden_items();
|
||||
hovered_entry = legend.hovered_entry_name();
|
||||
}
|
||||
|
||||
if let Some(group) = linked_axes.as_ref() {
|
||||
|
@ -1073,7 +1072,7 @@ pub struct GridMark {
|
|||
/// 10 is a typical value, others are possible though.
|
||||
pub fn log_grid_spacer(log_base: i64) -> GridSpacer {
|
||||
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
|
||||
// to the next-bigger power of 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)
|
||||
};
|
||||
|
||||
Box::new(get_step_sizes)
|
||||
Box::new(step_sizes)
|
||||
}
|
||||
|
||||
/// 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());
|
||||
plot_ui.set_clip_rect(*transform.frame());
|
||||
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() {
|
||||
|
|
|
@ -16,9 +16,9 @@ impl Custom3d {
|
|||
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
|
||||
// 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 {
|
||||
label: None,
|
||||
|
@ -56,7 +56,7 @@ impl Custom3d {
|
|||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(render_state.target_format.into())],
|
||||
targets: &[Some(wgpu_render_state.target_format.into())],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
|
@ -84,7 +84,7 @@ impl Custom3d {
|
|||
// 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
|
||||
// `paint_callback_resources` type map, which is stored alongside the render pass.
|
||||
render_state
|
||||
wgpu_render_state
|
||||
.egui_rpass
|
||||
.write()
|
||||
.paint_callback_resources
|
||||
|
|
|
@ -389,19 +389,19 @@ impl CustomAxisDemo {
|
|||
const MINS_PER_DAY: f64 = CustomAxisDemo::MINS_PER_DAY;
|
||||
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()
|
||||
}
|
||||
|
||||
fn get_hour(x: f64) -> f64 {
|
||||
fn hour(x: f64) -> f64 {
|
||||
(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()
|
||||
}
|
||||
|
||||
fn get_percent(y: f64) -> f64 {
|
||||
fn percent(y: f64) -> f64 {
|
||||
100.0 * y
|
||||
}
|
||||
|
||||
|
@ -411,17 +411,17 @@ impl CustomAxisDemo {
|
|||
String::new()
|
||||
} else if is_approx_integer(x / MINS_PER_DAY) {
|
||||
// Days
|
||||
format!("Day {}", get_day(x))
|
||||
format!("Day {}", day(x))
|
||||
} else {
|
||||
// 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>| {
|
||||
// Display only integer percentages
|
||||
if !is_approx_zero(y) && is_approx_integer(100.0 * y) {
|
||||
format!("{:.0}%", get_percent(y))
|
||||
format!("{:.0}%", percent(y))
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
|
@ -430,10 +430,10 @@ impl CustomAxisDemo {
|
|||
let label_fmt = |_s: &str, val: &PlotPoint| {
|
||||
format!(
|
||||
"Day {d}, {h}:{m:02}\n{p:.2}%",
|
||||
d = get_day(val.x),
|
||||
h = get_hour(val.x),
|
||||
m = get_minute(val.x),
|
||||
p = get_percent(val.y)
|
||||
d = day(val.x),
|
||||
h = hour(val.x),
|
||||
m = minute(val.x),
|
||||
p = percent(val.y)
|
||||
)
|
||||
};
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ image = { version = "0.24", optional = true, default-features = false }
|
|||
|
||||
# svg feature
|
||||
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 }
|
||||
|
||||
# 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 }
|
||||
|
||||
ahash = "0.7"
|
||||
ahash = "0.8"
|
||||
bytemuck = "1.7"
|
||||
glium = "0.31"
|
||||
glium = "0.32"
|
||||
|
||||
#! ### Optional dependencies
|
||||
## Enable this when generating docs.
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use glium::glutin;
|
||||
|
||||
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 mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
use glium::glutin;
|
||||
|
||||
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 mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop);
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
use egui::epaint::Primitive;
|
||||
|
||||
use {
|
||||
ahash::AHashMap,
|
||||
egui::{emath::Rect, epaint::Mesh},
|
||||
glium::{
|
||||
implement_vertex,
|
||||
|
@ -21,7 +20,7 @@ pub struct Painter {
|
|||
max_texture_side: usize,
|
||||
program: glium::Program,
|
||||
|
||||
textures: AHashMap<egui::TextureId, Rc<SrgbTexture2d>>,
|
||||
textures: ahash::HashMap<egui::TextureId, Rc<SrgbTexture2d>>,
|
||||
|
||||
/// [`egui::TextureId::User`] index
|
||||
next_native_tex_id: u64,
|
||||
|
@ -145,7 +144,7 @@ impl Painter {
|
|||
let width_in_points = width_in_pixels as f32 / pixels_per_point;
|
||||
let height_in_points = height_in_pixels as f32 / pixels_per_point;
|
||||
|
||||
if let Some(texture) = self.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.
|
||||
// For user textures linear sampling is more likely to be the right choice.
|
||||
let filter = MagnifySamplerFilter::Linear;
|
||||
|
@ -274,7 +273,7 @@ impl Painter {
|
|||
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())
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ wasm-bindgen = { version = "0.2" }
|
|||
|
||||
|
||||
[dev-dependencies]
|
||||
glutin = "0.28.0" # examples/pure_glow
|
||||
glutin = "0.29.0" # examples/pure_glow
|
||||
|
||||
|
||||
[[example]]
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
fn main() {
|
||||
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 = std::sync::Arc::new(gl);
|
||||
|
||||
|
|
|
@ -452,7 +452,7 @@ impl Painter {
|
|||
#[inline(never)] // Easier profiling
|
||||
fn paint_mesh(&mut self, mesh: &Mesh) {
|
||||
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 {
|
||||
self.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
|
||||
self.gl.buffer_data_u8_slice(
|
||||
|
@ -634,10 +634,15 @@ impl Painter {
|
|||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
#[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
|
||||
pub fn register_native_texture(&mut self, native: glow::Texture) -> egui::TextureId {
|
||||
self.assert_not_destroyed();
|
||||
|
|
|
@ -56,7 +56,7 @@ serde = ["dep:serde", "ahash/serde", "emath/serde"]
|
|||
emath = { version = "0.18.0", path = "../emath" }
|
||||
|
||||
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"
|
||||
|
||||
#! ### Optional dependencies
|
||||
|
|
|
@ -13,6 +13,11 @@ pub use crate::{CubicBezierShape, QuadraticBezierShape};
|
|||
|
||||
/// A paint primitive such as a circle or a piece of text.
|
||||
/// 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"]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Shape {
|
||||
|
@ -37,6 +42,8 @@ pub enum Shape {
|
|||
Rect(RectShape),
|
||||
|
||||
/// Text.
|
||||
///
|
||||
/// This needs to be recreated if `pixels_per_point` (dpi scale) changes.
|
||||
Text(TextShape),
|
||||
|
||||
/// A general triangle mesh.
|
||||
|
@ -604,6 +611,8 @@ impl Rounding {
|
|||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// How to paint some text on screen.
|
||||
///
|
||||
/// This needs to be recreated if `pixels_per_point` (dpi scale) changes.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct TextShape {
|
||||
|
|
|
@ -1334,6 +1334,11 @@ impl Tessellator {
|
|||
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.indices.reserve(galley.num_indices);
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ use crate::{
|
|||
mutex::{Mutex, RwLock},
|
||||
TextureAtlas,
|
||||
};
|
||||
use ahash::AHashMap;
|
||||
use emath::{vec2, Vec2};
|
||||
use std::collections::BTreeSet;
|
||||
use std::sync::Arc;
|
||||
|
@ -66,7 +65,7 @@ pub struct FontImpl {
|
|||
// move each character by this much (hack)
|
||||
y_offset: 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>>,
|
||||
}
|
||||
|
||||
|
@ -221,7 +220,7 @@ pub struct Font {
|
|||
replacement_glyph: (FontIndex, GlyphInfo),
|
||||
pixels_per_point: f32,
|
||||
row_height: f32,
|
||||
glyph_info_cache: AHashMap<char, (FontIndex, GlyphInfo)>,
|
||||
glyph_info_cache: ahash::HashMap<char, (FontIndex, GlyphInfo)>,
|
||||
}
|
||||
|
||||
impl Font {
|
||||
|
|
|
@ -535,7 +535,7 @@ pub struct FontsImpl {
|
|||
definitions: FontDefinitions,
|
||||
atlas: Arc<Mutex<TextureAtlas>>,
|
||||
font_impl_cache: FontImplCache,
|
||||
sized_family: ahash::AHashMap<(u32, FontFamily), Font>,
|
||||
sized_family: ahash::HashMap<(u32, FontFamily), Font>,
|
||||
}
|
||||
|
||||
impl FontsImpl {
|
||||
|
@ -673,7 +673,7 @@ struct FontImplCache {
|
|||
ab_glyph_fonts: BTreeMap<String, (FontTweak, ab_glyph::FontArc)>,
|
||||
|
||||
/// 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 {
|
||||
|
|
|
@ -478,6 +478,7 @@ fn galley_from_rows(point_scale: PointScale, job: Arc<LayoutJob>, mut rows: Vec<
|
|||
mesh_bounds,
|
||||
num_vertices,
|
||||
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.
|
||||
///
|
||||
/// 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)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Galley {
|
||||
|
@ -341,6 +343,12 @@ pub struct Galley {
|
|||
|
||||
/// Total number of indices in all the row meshes.
|
||||
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)]
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use crate::{ImageData, ImageDelta, TextureId};
|
||||
use ahash::AHashMap;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
@ -11,7 +10,7 @@ pub struct TextureManager {
|
|||
/// We allocate texture id:s linearly.
|
||||
next_id: u64,
|
||||
/// Information about currently allocated textures.
|
||||
metas: AHashMap<TextureId, TextureMeta>,
|
||||
metas: ahash::HashMap<TextureId, TextureMeta>,
|
||||
delta: TexturesDelta,
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ pub use ordered_float::*;
|
|||
#[inline]
|
||||
pub fn hash(value: impl std::hash::Hash) -> u64 {
|
||||
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);
|
||||
hasher.finish()
|
||||
}
|
||||
|
|
|
@ -7,9 +7,16 @@ edition = "2021"
|
|||
rust-version = "1.61"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
eframe = { path = "../../eframe", features = ["glow"] }
|
||||
egui_glow = { path = "../../egui_glow" }
|
||||
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
|
||||
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
|
||||
|
||||
use eframe::egui;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(egui::vec2(550.0, 610.0)),
|
||||
|
@ -17,12 +19,12 @@ fn main() {
|
|||
);
|
||||
}
|
||||
|
||||
struct MyApp {
|
||||
pub struct MyApp {
|
||||
angle: f32,
|
||||
}
|
||||
|
||||
impl MyApp {
|
||||
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
Self { angle: 0.2 }
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +43,28 @@ impl eframe::App for MyApp {
|
|||
|
||||
egui::ScrollArea::both().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!");
|
||||
});
|
||||
|
@ -54,65 +77,68 @@ impl eframe::App for MyApp {
|
|||
}
|
||||
}
|
||||
|
||||
impl MyApp {
|
||||
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`].
|
||||
/// We get a [`glow::Context`] from `eframe` and we want to construct a [`ThreeDApp`].
|
||||
///
|
||||
/// Sadly we can't just create a [`three_d::Context`] in [`MyApp::new`] and pass it
|
||||
/// to the [`egui::PaintCallback`] because [`three_d::Context`] isn't `Send+Sync`, which
|
||||
/// [`egui::PaintCallback`] is.
|
||||
fn with_three_d_context<R>(
|
||||
gl: &std::sync::Arc<glow::Context>,
|
||||
f: impl FnOnce(&three_d::Context) -> R,
|
||||
) -> R {
|
||||
/// Sadly we can't just create a [`ThreeDApp`] in [`MyApp::new`] and pass it
|
||||
/// to the [`egui::PaintCallback`] because [`glow::Context`] isn't `Send+Sync` on web, which
|
||||
/// [`egui::PaintCallback`] needs. If you do not target web, then you can construct the [`ThreeDApp`] in [`MyApp::new`].
|
||||
fn with_three_d<R>(gl: &std::sync::Arc<glow::Context>, f: impl FnOnce(&mut ThreeDApp) -> R) -> R {
|
||||
use std::cell::RefCell;
|
||||
thread_local! {
|
||||
pub static THREE_D: RefCell<Option<three_d::Context>> = 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);
|
||||
pub static THREE_D: RefCell<Option<ThreeDApp>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
THREE_D.with(|three_d| {
|
||||
let mut three_d = three_d.borrow_mut();
|
||||
let three_d =
|
||||
three_d.get_or_insert_with(|| three_d::Context::from_gl_context(gl.clone()).unwrap());
|
||||
let three_d = three_d.get_or_insert_with(|| ThreeDApp::new(gl.clone()));
|
||||
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::*;
|
||||
|
||||
// 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
|
||||
let viewport = info.viewport_in_pixels();
|
||||
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`).
|
||||
let clip_rect = info.clip_rect_in_pixels();
|
||||
three_d.set_scissor(ScissorBox {
|
||||
let scissor_box = ScissorBox {
|
||||
x: clip_rect.left_px.round() as _,
|
||||
y: clip_rect.from_bottom_px.round() as _,
|
||||
width: clip_rect.width_px.round() as _,
|
||||
height: clip_rect.height_px.round() as _,
|
||||
});
|
||||
|
||||
let camera = Camera::new_perspective(
|
||||
three_d,
|
||||
};
|
||||
Self {
|
||||
screen,
|
||||
scissor_box,
|
||||
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, 0.0),
|
||||
vec3(0.0, 1.0, 0.0),
|
||||
degrees(45.0),
|
||||
0.1,
|
||||
10.0,
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
// Create a CPU-side mesh consisting of a single colored triangle
|
||||
let positions = vec![
|
||||
|
@ -160,14 +206,31 @@ fn paint_with_three_d(three_d: &three_d::Context, info: &egui::PaintCallbackInfo
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
let mut model = Gm::new(
|
||||
Mesh::new(three_d, &cpu_mesh).unwrap(),
|
||||
ColorMaterial::default(),
|
||||
);
|
||||
// Construct a model, with a default color material, thereby transferring the mesh data to the GPU
|
||||
let model = Gm::new(Mesh::new(&context, &cpu_mesh), 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
|
||||
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
|
||||
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
|
||||
};
|
||||
let title_bar_response =
|
||||
ui.interact(title_bar_rect, Id::new("title_bar"), Sense::drag());
|
||||
if title_bar_response.drag_started() {
|
||||
ui.interact(title_bar_rect, Id::new("title_bar"), Sense::click());
|
||||
if title_bar_response.is_pointer_button_down_on() {
|
||||
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