Merge branch 'master' into web_handle

This commit is contained in:
Emil Ernerfeldt 2022-08-20 09:59:18 +02:00
commit d3e26cd00a
59 changed files with 1727 additions and 1094 deletions

View file

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

@ -2,3 +2,4 @@
/.*.json
/.vscode
/media/*
.DS_Store

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -49,16 +49,17 @@ confidence-threshold = 0.92 # We want really high confidence when inferring lice
copyleft = "deny"
allow = [
# "Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
"ISC", # https://tldrlegal.com/license/-isc-license
"MIT", # https://tldrlegal.com/license/mit-license
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11
"OpenSSL", # https://www.openssl.org/source/license.html
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
"ISC", # https://tldrlegal.com/license/-isc-license
"MIT", # https://tldrlegal.com/license/mit-license
"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)
]
[[licenses.clarify]]

View file

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

View file

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

View file

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

View file

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

View file

@ -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,73 +56,291 @@ fn create_display(
pub use epi::NativeOptions;
/// Run an egui app
#[cfg(feature = "glow")]
pub fn run_glow(
app_name: &str,
native_options: &epi::NativeOptions,
app_creator: epi::AppCreator,
) -> ! {
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();
enum EventResult {
Wait,
RepaintAsap,
RepaintAt(Instant),
Exit,
}
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);
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;
}
let mut painter = egui_glow::Painter::new(gl.clone(), None, "")
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
/// 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()));
let system_theme = native_options.system_theme();
let mut integration = epi_integration::EpiIntegration::new(
&event_loop,
painter.max_texture_side(),
gl_window.window(),
system_theme,
storage,
Some(gl.clone()),
#[cfg(feature = "wgpu")]
None,
);
let theme = system_theme.unwrap_or(native_options.default_theme);
integration.egui_ctx.set_visuals(theme.egui_visuals());
EVENT_LOOP.with(|event_loop| {
f(&mut *event_loop.borrow_mut());
});
}
{
let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy());
integration.egui_ctx.set_request_repaint_callback(move || {
event_loop_proxy.lock().send_event(RequestRepaintEvent).ok();
});
}
fn run_and_return(event_loop: &mut EventLoop<RequestRepaintEvent>, mut winit_app: impl WinitApp) {
use winit::platform::run_return::EventLoopExtRunReturn as _;
let mut app = app_creator(&epi::CreationContext {
egui_ctx: integration.egui_ctx.clone(),
integration_info: integration.frame.info(),
storage: integration.frame.storage(),
gl: Some(gl.clone()),
#[cfg(feature = "wgpu")]
render_state: None,
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)
}
}
});
if app.warm_up_enabled() {
integration.warm_up(app.as_mut(), gl_window.window());
}
tracing::debug!("eframe window closed");
let mut is_focused = true;
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 window = gl_window.window();
let event_result = match event {
winit::event::Event::LoopDestroyed => EventResult::Exit,
let mut redraw = || {
// 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")]
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 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 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,
painter.max_texture_side(),
gl_window.window(),
system_theme,
storage,
Some(gl.clone()),
#[cfg(feature = "wgpu")]
None,
);
let theme = system_theme.unwrap_or(native_options.default_theme);
integration.egui_ctx.set_visuals(theme.egui_visuals());
{
let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy());
integration.egui_ctx.set_request_repaint_callback(move || {
event_loop_proxy.lock().send_event(RequestRepaintEvent).ok();
});
}
let mut app = app_creator(&epi::CreationContext {
egui_ctx: integration.egui_ctx.clone(),
integration_info: integration.frame.info(),
storage: integration.frame.storage(),
gl: Some(gl.clone()),
#[cfg(feature = "wgpu")]
wgpu_render_state: None,
});
if app.warm_up_enabled() {
integration.warm_up(app.as_mut(), gl_window.window());
}
Self {
gl_window,
gl,
painter,
integration,
app,
is_focused: true,
}
}
}
impl WinitApp for GlowWinitApp {
fn is_focused(&self) -> bool {
self.is_focused
}
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,140 +401,206 @@ 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(),
winit::event::Event::WindowEvent { event, .. } => {
match &event {
winit::event::WindowEvent::Focused(new_focused) => {
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);
}
}
winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
gl_window.resize(**new_inner_size);
}
winit::event::WindowEvent::CloseRequested if integration.should_quit() => {
*control_flow = winit::event_loop::ControlFlow::Exit;
}
_ => {}
}
integration.on_event(app.as_mut(), &event);
if integration.should_quit() {
*control_flow = winit::event_loop::ControlFlow::Exit;
}
window.request_redraw(); // TODO(emilk): ask egui if the events warrants a repaint instead
}
winit::event::Event::LoopDestroyed => {
integration.save(&mut *app, window);
app.on_exit(Some(&gl));
painter.destroy();
}
winit::event::Event::UserEvent(RequestRepaintEvent) => window.request_redraw(),
winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
..
}) => {
window.request_redraw();
}
_ => {}
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) => {
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 {
self.gl_window.resize(*physical_size);
}
}
winit::event::WindowEvent::ScaleFactorChanged {
new_inner_size, ..
} => {
self.gl_window.resize(**new_inner_size);
}
winit::event::WindowEvent::CloseRequested
if self.integration.should_quit() =>
{
return EventResult::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,
}
}
}
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);
}
}
}
// TODO(emilk): merge with with the clone above
/// Run an egui app
#[cfg(feature = "glow")]
pub use glow_integration::run_glow;
// ----------------------------------------------------------------------------
#[cfg(feature = "wgpu")]
pub fn run_wgpu(
app_name: &str,
native_options: &epi::NativeOptions,
app_creator: epi::AppCreator,
) -> ! {
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();
mod wgpu_integration {
use super::*;
let window = epi_integration::window_builder(native_options, &window_settings)
.with_title(app_name)
.build(&event_loop)
.unwrap();
// SAFETY: `window` must outlive `painter`.
#[allow(unsafe_code)]
let mut painter = unsafe {
let mut painter = egui_wgpu::winit::Painter::new(
wgpu::Backends::PRIMARY | wgpu::Backends::GL,
wgpu::PowerPreference::HighPerformance,
wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::default(),
limits: wgpu::Limits::default(),
},
wgpu::PresentMode::Fifo,
native_options.multisampling.max(1) as _,
);
#[cfg(not(target_os = "android"))]
painter.set_window(Some(&window));
painter
};
let render_state = painter.get_render_state().expect("Uninitialized");
let system_theme = native_options.system_theme();
let mut integration = epi_integration::EpiIntegration::new(
&event_loop,
painter.max_texture_side().unwrap_or(2048),
&window,
system_theme,
storage,
#[cfg(feature = "glow")]
None,
Some(render_state.clone()),
);
let theme = system_theme.unwrap_or(native_options.default_theme);
integration.egui_ctx.set_visuals(theme.egui_visuals());
{
let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy());
integration.egui_ctx.set_request_repaint_callback(move || {
event_loop_proxy.lock().send_event(RequestRepaintEvent).ok();
});
struct WgpuWinitApp {
window: winit::window::Window,
painter: egui_wgpu::winit::Painter<'static>,
integration: epi_integration::EpiIntegration,
app: Box<dyn epi::App>,
is_focused: bool,
}
let mut app = app_creator(&epi::CreationContext {
egui_ctx: integration.egui_ctx.clone(),
integration_info: integration.frame.info(),
storage: integration.frame.storage(),
#[cfg(feature = "glow")]
gl: None,
render_state: Some(render_state),
});
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());
if app.warm_up_enabled() {
integration.warm_up(app.as_mut(), &window);
let window = epi_integration::window_builder(native_options, &window_settings)
.with_title(app_name)
.build(event_loop)
.unwrap();
// SAFETY: `window` must outlive `painter`.
#[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,
wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::default(),
limits: wgpu::Limits::default(),
},
wgpu::PresentMode::Fifo,
native_options.multisampling.max(1) as _,
);
#[cfg(not(target_os = "android"))]
painter.set_window(Some(&window));
painter
};
let wgpu_render_state = painter.render_state();
let system_theme = native_options.system_theme();
let mut integration = epi_integration::EpiIntegration::new(
event_loop,
painter.max_texture_side().unwrap_or(2048),
&window,
system_theme,
storage,
#[cfg(feature = "glow")]
None,
wgpu_render_state.clone(),
);
let theme = system_theme.unwrap_or(native_options.default_theme);
integration.egui_ctx.set_visuals(theme.egui_visuals());
{
let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy());
integration.egui_ctx.set_request_repaint_callback(move || {
event_loop_proxy.lock().send_event(RequestRepaintEvent).ok();
});
}
let mut app = app_creator(&epi::CreationContext {
egui_ctx: integration.egui_ctx.clone(),
integration_info: integration.frame.info(),
storage: integration.frame.storage(),
#[cfg(feature = "glow")]
gl: None,
wgpu_render_state,
});
if app.warm_up_enabled() {
integration.warm_up(app.as_mut(), &window);
}
Self {
window,
painter,
integration,
app,
is_focused: true,
}
}
}
let mut is_focused = true;
impl WinitApp for WgpuWinitApp {
fn is_focused(&self) -> bool {
self.is_focused
}
event_loop.run(move |event, _, control_flow| {
let window = &window;
fn integration(&self) -> &EpiIntegration {
&self.integration
}
let mut redraw = || {
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));
}
};
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));
},
#[cfg(target_os = "android")]
winit::event::Event::Paused => unsafe {
painter.set_window(None);
},
winit::event::Event::WindowEvent { event, .. } => {
match &event {
winit::event::WindowEvent::Focused(new_focused) => {
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);
}
}
winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
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;
}
_ => {}
};
integration.on_event(app.as_mut(), &event);
if integration.should_quit() {
*control_flow = winit::event_loop::ControlFlow::Exit;
}
window.request_redraw(); // TODO(emilk): ask egui if the events warrants a repaint instead
}
winit::event::Event::LoopDestroyed => {
integration.save(&mut *app, window);
#[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();
}
_ => (),
control_flow
}
});
fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult {
match event {
#[cfg(target_os = "android")]
winit::event::Event::Resumed => unsafe {
self.painter.set_window(Some(&self.window));
EventResult::RepaintAsap
},
#[cfg(target_os = "android")]
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) => {
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 {
self.painter
.on_window_resized(physical_size.width, physical_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 self.integration.should_quit() =>
{
return EventResult::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,
}
}
}
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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

@ -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,120 +77,160 @@ 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
use three_d::*;
// Set where to paint
let viewport = info.viewport_in_pixels();
let viewport = Viewport {
x: viewport.left_px.round() as _,
y: viewport.from_bottom_px.round() as _,
width: viewport.width_px.round() as _,
height: viewport.height_px.round() as _,
};
// 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 {
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,
viewport,
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![
vec3(0.5, -0.5, 0.0), // bottom right
vec3(-0.5, -0.5, 0.0), // bottom left
vec3(0.0, 0.5, 0.0), // top
];
let colors = vec![
Color::new(255, 0, 0, 255), // bottom right
Color::new(0, 255, 0, 255), // bottom left
Color::new(0, 0, 255, 255), // top
];
let cpu_mesh = CpuMesh {
positions: Positions::F32(positions),
colors: Some(colors),
..Default::default()
};
let mut model = Gm::new(
Mesh::new(three_d, &cpu_mesh).unwrap(),
ColorMaterial::default(),
);
// Set the current transformation of the triangle
model.set_transformation(Mat4::from_angle_y(radians(angle)));
// Render the triangle with the color material which uses the per vertex colors defined at construction
model.render(&camera, &[]).unwrap();
///
/// 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 {
x: viewport.left_px.round() as _,
y: viewport.from_bottom_px.round() as _,
width: viewport.width_px.round() as _,
height: viewport.height_px.round() as _,
};
// Respect the egui clip region (e.g. if we are inside an `egui::ScrollArea`).
let clip_rect = info.clip_rect_in_pixels();
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 _,
};
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,
);
// Create a CPU-side mesh consisting of a single colored triangle
let positions = vec![
vec3(0.5, -0.5, 0.0), // bottom right
vec3(-0.5, -0.5, 0.0), // bottom left
vec3(0.0, 0.5, 0.0), // top
];
let colors = vec![
Color::new(255, 0, 0, 255), // bottom right
Color::new(0, 255, 0, 255), // bottom left
Color::new(0, 0, 255, 255), // top
];
let cpu_mesh = CpuMesh {
positions: Positions::F32(positions),
colors: Some(colors),
..Default::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
self.model
.set_transformation(Mat4::from_angle_y(radians(angle)));
// Get the screen render target to be able to render something on the screen
frame_input
.screen
// Clear the color and depth of the screen render target
.clear_partially(frame_input.scissor_box, ClearState::depth(1.0))
// Render the triangle with the color material which uses the per vertex colors defined at construction
.render_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.
}
}

View file

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

View 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" }

View 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
```

View 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();
}
});
}
}