2022-02-22 16:13:53 +00:00
|
|
|
pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
|
2022-02-02 15:47:27 +00:00
|
|
|
winit::dpi::LogicalSize {
|
|
|
|
width: points.x as f64,
|
|
|
|
height: points.y as f64,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-19 19:40:55 +00:00
|
|
|
pub fn window_builder(
|
|
|
|
native_options: &epi::NativeOptions,
|
|
|
|
window_settings: &Option<crate::WindowSettings>,
|
|
|
|
) -> winit::window::WindowBuilder {
|
2022-02-05 18:12:03 +00:00
|
|
|
let epi::NativeOptions {
|
|
|
|
always_on_top,
|
|
|
|
maximized,
|
|
|
|
decorated,
|
|
|
|
drag_and_drop_support,
|
|
|
|
icon_data,
|
|
|
|
initial_window_pos,
|
|
|
|
initial_window_size,
|
|
|
|
min_window_size,
|
|
|
|
max_window_size,
|
|
|
|
resizable,
|
|
|
|
transparent,
|
|
|
|
} = native_options;
|
|
|
|
|
|
|
|
let window_icon = icon_data.clone().and_then(load_icon);
|
2021-10-19 19:40:55 +00:00
|
|
|
|
|
|
|
let mut window_builder = winit::window::WindowBuilder::new()
|
2022-02-05 18:12:03 +00:00
|
|
|
.with_always_on_top(*always_on_top)
|
|
|
|
.with_maximized(*maximized)
|
|
|
|
.with_decorations(*decorated)
|
|
|
|
.with_resizable(*resizable)
|
|
|
|
.with_transparent(*transparent)
|
2021-10-19 19:40:55 +00:00
|
|
|
.with_window_icon(window_icon);
|
|
|
|
|
2022-02-05 18:12:03 +00:00
|
|
|
if let Some(min_size) = *min_window_size {
|
2022-02-02 15:47:27 +00:00
|
|
|
window_builder = window_builder.with_min_inner_size(points_to_size(min_size));
|
|
|
|
}
|
2022-02-05 18:12:03 +00:00
|
|
|
if let Some(max_size) = *max_window_size {
|
2022-02-02 15:47:27 +00:00
|
|
|
window_builder = window_builder.with_max_inner_size(points_to_size(max_size));
|
|
|
|
}
|
|
|
|
|
2022-02-05 18:12:03 +00:00
|
|
|
window_builder = window_builder_drag_and_drop(window_builder, *drag_and_drop_support);
|
2021-10-19 19:40:55 +00:00
|
|
|
|
|
|
|
if let Some(window_settings) = window_settings {
|
|
|
|
window_builder = window_settings.initialize_window(window_builder);
|
2022-02-05 18:12:03 +00:00
|
|
|
} else {
|
|
|
|
if let Some(pos) = *initial_window_pos {
|
|
|
|
window_builder = window_builder.with_position(winit::dpi::PhysicalPosition {
|
|
|
|
x: pos.x as f64,
|
|
|
|
y: pos.y as f64,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if let Some(initial_window_size) = *initial_window_size {
|
|
|
|
window_builder = window_builder.with_inner_size(points_to_size(initial_window_size));
|
|
|
|
}
|
2021-10-19 19:40:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
window_builder
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_icon(icon_data: epi::IconData) -> Option<winit::window::Icon> {
|
|
|
|
winit::window::Icon::from_rgba(icon_data.rgba, icon_data.width, icon_data.height).ok()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(target_os = "windows")]
|
|
|
|
fn window_builder_drag_and_drop(
|
|
|
|
window_builder: winit::window::WindowBuilder,
|
|
|
|
enable: bool,
|
|
|
|
) -> winit::window::WindowBuilder {
|
2021-10-20 07:51:21 +00:00
|
|
|
use winit::platform::windows::WindowBuilderExtWindows as _;
|
2021-10-19 19:40:55 +00:00
|
|
|
window_builder.with_drag_and_drop(enable)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
|
|
fn window_builder_drag_and_drop(
|
|
|
|
window_builder: winit::window::WindowBuilder,
|
|
|
|
_enable: bool,
|
|
|
|
) -> winit::window::WindowBuilder {
|
|
|
|
// drag and drop can only be disabled on windows
|
|
|
|
window_builder
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_app_output(
|
|
|
|
window: &winit::window::Window,
|
|
|
|
current_pixels_per_point: f32,
|
|
|
|
app_output: epi::backend::AppOutput,
|
2022-01-15 12:59:52 +00:00
|
|
|
) {
|
2021-10-19 19:40:55 +00:00
|
|
|
let epi::backend::AppOutput {
|
|
|
|
quit: _,
|
|
|
|
window_size,
|
2021-10-22 22:03:17 +00:00
|
|
|
window_title,
|
2021-10-19 19:40:55 +00:00
|
|
|
decorated,
|
|
|
|
drag_window,
|
|
|
|
} = app_output;
|
|
|
|
|
|
|
|
if let Some(decorated) = decorated {
|
|
|
|
window.set_decorations(decorated);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(window_size) = window_size {
|
|
|
|
window.set_inner_size(
|
|
|
|
winit::dpi::PhysicalSize {
|
|
|
|
width: (current_pixels_per_point * window_size.x).round(),
|
|
|
|
height: (current_pixels_per_point * window_size.y).round(),
|
|
|
|
}
|
|
|
|
.to_logical::<f32>(crate::native_pixels_per_point(window) as f64),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-10-22 22:03:17 +00:00
|
|
|
if let Some(window_title) = window_title {
|
|
|
|
window.set_title(&window_title);
|
|
|
|
}
|
|
|
|
|
2021-10-19 19:40:55 +00:00
|
|
|
if drag_window {
|
|
|
|
let _ = window.drag_window();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
/// For loading/saving app state and/or egui memory to disk.
|
|
|
|
pub struct Persistence {
|
|
|
|
storage: Option<Box<dyn epi::Storage>>,
|
2022-01-03 21:13:53 +00:00
|
|
|
last_auto_save: instant::Instant,
|
2021-10-19 19:40:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::unused_self)]
|
|
|
|
impl Persistence {
|
|
|
|
#[cfg(feature = "persistence")]
|
|
|
|
const EGUI_MEMORY_KEY: &'static str = "egui";
|
|
|
|
#[cfg(feature = "persistence")]
|
|
|
|
const WINDOW_KEY: &'static str = "window";
|
|
|
|
|
|
|
|
pub fn from_app_name(app_name: &str) -> Self {
|
|
|
|
fn create_storage(_app_name: &str) -> Option<Box<dyn epi::Storage>> {
|
|
|
|
#[cfg(feature = "persistence")]
|
|
|
|
if let Some(storage) = epi::file_storage::FileStorage::from_app_name(_app_name) {
|
|
|
|
return Some(Box::new(storage));
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
Self {
|
|
|
|
storage: create_storage(app_name),
|
2022-01-03 21:13:53 +00:00
|
|
|
last_auto_save: instant::Instant::now(),
|
2021-10-19 19:40:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn storage(&self) -> Option<&dyn epi::Storage> {
|
|
|
|
self.storage.as_deref()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "persistence")]
|
|
|
|
pub fn load_window_settings(&self) -> Option<crate::WindowSettings> {
|
|
|
|
epi::get_value(&**self.storage.as_ref()?, Self::WINDOW_KEY)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(feature = "persistence"))]
|
|
|
|
pub fn load_window_settings(&self) -> Option<crate::WindowSettings> {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "persistence")]
|
|
|
|
pub fn load_memory(&self) -> Option<egui::Memory> {
|
|
|
|
epi::get_value(&**self.storage.as_ref()?, Self::EGUI_MEMORY_KEY)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(feature = "persistence"))]
|
|
|
|
pub fn load_memory(&self) -> Option<egui::Memory> {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn save(
|
|
|
|
&mut self,
|
|
|
|
_app: &mut dyn epi::App,
|
|
|
|
_egui_ctx: &egui::Context,
|
|
|
|
_window: &winit::window::Window,
|
|
|
|
) {
|
|
|
|
#[cfg(feature = "persistence")]
|
|
|
|
if let Some(storage) = &mut self.storage {
|
|
|
|
if _app.persist_native_window() {
|
|
|
|
epi::set_value(
|
|
|
|
storage.as_mut(),
|
|
|
|
Self::WINDOW_KEY,
|
|
|
|
&crate::WindowSettings::from_display(_window),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if _app.persist_egui_memory() {
|
|
|
|
epi::set_value(
|
|
|
|
storage.as_mut(),
|
|
|
|
Self::EGUI_MEMORY_KEY,
|
|
|
|
&*_egui_ctx.memory(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
_app.save(storage.as_mut());
|
|
|
|
storage.flush();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn maybe_autosave(
|
|
|
|
&mut self,
|
|
|
|
app: &mut dyn epi::App,
|
|
|
|
egui_ctx: &egui::Context,
|
|
|
|
window: &winit::window::Window,
|
|
|
|
) {
|
2022-01-03 21:13:53 +00:00
|
|
|
let now = instant::Instant::now();
|
2021-10-19 19:40:55 +00:00
|
|
|
if now - self.last_auto_save > app.auto_save_interval() {
|
|
|
|
self.save(app, egui_ctx, window);
|
|
|
|
self.last_auto_save = now;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-11-03 12:45:51 +00:00
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
/// Everything needed to make a winit-based integration for [`epi`].
|
|
|
|
pub struct EpiIntegration {
|
2021-12-26 20:21:28 +00:00
|
|
|
frame: epi::Frame,
|
2021-11-03 12:45:51 +00:00
|
|
|
persistence: crate::epi::Persistence,
|
2022-01-10 22:13:10 +00:00
|
|
|
pub egui_ctx: egui::Context,
|
2022-02-22 16:13:53 +00:00
|
|
|
pending_full_output: egui::FullOutput,
|
2021-11-03 12:45:51 +00:00
|
|
|
egui_winit: crate::State,
|
|
|
|
pub app: Box<dyn epi::App>,
|
|
|
|
/// When set, it is time to quit
|
|
|
|
quit: bool,
|
2022-01-26 21:04:24 +00:00
|
|
|
can_drag_window: bool,
|
2021-11-03 12:45:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl EpiIntegration {
|
|
|
|
pub fn new(
|
|
|
|
integration_name: &'static str,
|
2022-01-24 13:32:36 +00:00
|
|
|
max_texture_side: usize,
|
2021-11-03 12:45:51 +00:00
|
|
|
window: &winit::window::Window,
|
2022-03-14 12:25:11 +00:00
|
|
|
gl: &std::rc::Rc<glow::Context>,
|
2021-11-03 12:45:51 +00:00
|
|
|
persistence: crate::epi::Persistence,
|
|
|
|
app: Box<dyn epi::App>,
|
|
|
|
) -> Self {
|
2022-01-10 22:13:10 +00:00
|
|
|
let egui_ctx = egui::Context::default();
|
2021-11-03 12:45:51 +00:00
|
|
|
|
|
|
|
*egui_ctx.memory() = persistence.load_memory().unwrap_or_default();
|
|
|
|
|
2022-02-02 16:09:36 +00:00
|
|
|
let prefer_dark_mode = prefer_dark_mode();
|
|
|
|
|
2021-12-26 20:21:28 +00:00
|
|
|
let frame = epi::Frame::new(epi::backend::FrameData {
|
|
|
|
info: epi::IntegrationInfo {
|
|
|
|
name: integration_name,
|
|
|
|
web_info: None,
|
2022-02-02 16:09:36 +00:00
|
|
|
prefer_dark_mode,
|
2021-12-26 20:21:28 +00:00
|
|
|
cpu_usage: None,
|
|
|
|
native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
|
|
|
|
},
|
|
|
|
output: Default::default(),
|
|
|
|
});
|
|
|
|
|
2022-02-02 16:09:36 +00:00
|
|
|
if prefer_dark_mode == Some(true) {
|
|
|
|
egui_ctx.set_visuals(egui::Visuals::dark());
|
|
|
|
} else {
|
|
|
|
egui_ctx.set_visuals(egui::Visuals::light());
|
|
|
|
}
|
|
|
|
|
2021-11-03 12:45:51 +00:00
|
|
|
let mut slf = Self {
|
2021-12-26 20:21:28 +00:00
|
|
|
frame,
|
2021-11-03 12:45:51 +00:00
|
|
|
persistence,
|
|
|
|
egui_ctx,
|
2022-01-24 13:32:36 +00:00
|
|
|
egui_winit: crate::State::new(max_texture_side, window),
|
2022-02-22 16:13:53 +00:00
|
|
|
pending_full_output: Default::default(),
|
2021-11-03 12:45:51 +00:00
|
|
|
app,
|
|
|
|
quit: false,
|
2022-01-26 21:04:24 +00:00
|
|
|
can_drag_window: false,
|
2021-11-03 12:45:51 +00:00
|
|
|
};
|
|
|
|
|
2022-03-14 12:25:11 +00:00
|
|
|
slf.setup(window, gl);
|
2021-11-03 12:45:51 +00:00
|
|
|
if slf.app.warm_up_enabled() {
|
2021-12-26 20:21:28 +00:00
|
|
|
slf.warm_up(window);
|
2021-11-03 12:45:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
slf
|
|
|
|
}
|
|
|
|
|
2022-03-14 12:25:11 +00:00
|
|
|
fn setup(&mut self, window: &winit::window::Window, gl: &std::rc::Rc<glow::Context>) {
|
2021-11-03 12:45:51 +00:00
|
|
|
self.app
|
2022-03-14 12:25:11 +00:00
|
|
|
.setup(&self.egui_ctx, &self.frame, self.persistence.storage(), gl);
|
2021-12-26 20:21:28 +00:00
|
|
|
let app_output = self.frame.take_app_output();
|
2022-01-17 17:45:09 +00:00
|
|
|
|
|
|
|
if app_output.quit {
|
|
|
|
self.quit = self.app.on_exit_event();
|
|
|
|
}
|
|
|
|
|
2022-01-15 12:59:52 +00:00
|
|
|
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
2021-11-03 12:45:51 +00:00
|
|
|
}
|
|
|
|
|
2021-12-26 20:21:28 +00:00
|
|
|
fn warm_up(&mut self, window: &winit::window::Window) {
|
2022-01-10 22:13:10 +00:00
|
|
|
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
|
2021-11-03 12:45:51 +00:00
|
|
|
self.egui_ctx.memory().set_everything_is_visible(true);
|
2022-02-22 16:13:53 +00:00
|
|
|
let full_output = self.update(window);
|
|
|
|
self.pending_full_output.append(full_output); // Handle it next frame
|
2021-11-03 12:45:51 +00:00
|
|
|
*self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
|
|
|
|
self.egui_ctx.clear_animations();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// If `true`, it is time to shut down.
|
|
|
|
pub fn should_quit(&self) -> bool {
|
|
|
|
self.quit
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn on_event(&mut self, event: &winit::event::WindowEvent<'_>) {
|
2022-01-26 21:04:24 +00:00
|
|
|
use winit::event::{ElementState, MouseButton, WindowEvent};
|
|
|
|
|
|
|
|
match event {
|
|
|
|
WindowEvent::CloseRequested => self.quit = self.app.on_exit_event(),
|
|
|
|
WindowEvent::Destroyed => self.quit = true,
|
|
|
|
WindowEvent::MouseInput {
|
|
|
|
button: MouseButton::Left,
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
..
|
|
|
|
} => self.can_drag_window = true,
|
|
|
|
_ => {}
|
2022-01-17 17:45:09 +00:00
|
|
|
}
|
|
|
|
|
2021-11-03 12:45:51 +00:00
|
|
|
self.egui_winit.on_event(&self.egui_ctx, event);
|
|
|
|
}
|
|
|
|
|
2022-02-22 16:13:53 +00:00
|
|
|
pub fn update(&mut self, window: &winit::window::Window) -> egui::FullOutput {
|
2022-01-03 21:13:53 +00:00
|
|
|
let frame_start = instant::Instant::now();
|
2021-11-03 12:45:51 +00:00
|
|
|
|
|
|
|
let raw_input = self.egui_winit.take_egui_input(window);
|
2022-02-22 16:13:53 +00:00
|
|
|
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
2021-12-26 20:21:28 +00:00
|
|
|
self.app.update(egui_ctx, &self.frame);
|
2021-11-03 19:11:25 +00:00
|
|
|
});
|
2022-02-22 16:13:53 +00:00
|
|
|
self.pending_full_output.append(full_output);
|
|
|
|
let full_output = std::mem::take(&mut self.pending_full_output);
|
|
|
|
|
|
|
|
{
|
|
|
|
let mut app_output = self.frame.take_app_output();
|
|
|
|
app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108
|
|
|
|
self.can_drag_window = false;
|
|
|
|
if app_output.quit {
|
|
|
|
self.quit = self.app.on_exit_event();
|
|
|
|
}
|
|
|
|
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
2022-01-17 17:45:09 +00:00
|
|
|
}
|
2022-01-15 12:59:52 +00:00
|
|
|
|
2022-01-03 21:13:53 +00:00
|
|
|
let frame_time = (instant::Instant::now() - frame_start).as_secs_f64() as f32;
|
2021-12-26 20:21:28 +00:00
|
|
|
self.frame.lock().info.cpu_usage = Some(frame_time);
|
2021-11-03 12:45:51 +00:00
|
|
|
|
2022-02-22 16:13:53 +00:00
|
|
|
full_output
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_platform_output(
|
|
|
|
&mut self,
|
|
|
|
window: &winit::window::Window,
|
|
|
|
platform_output: egui::PlatformOutput,
|
|
|
|
) {
|
|
|
|
self.egui_winit
|
|
|
|
.handle_platform_output(window, &self.egui_ctx, platform_output);
|
2021-11-03 12:45:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn maybe_autosave(&mut self, window: &winit::window::Window) {
|
|
|
|
self.persistence
|
|
|
|
.maybe_autosave(&mut *self.app, &self.egui_ctx, window);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn on_exit(&mut self, window: &winit::window::Window) {
|
|
|
|
self.app.on_exit();
|
|
|
|
self.persistence
|
|
|
|
.save(&mut *self.app, &self.egui_ctx, window);
|
|
|
|
}
|
|
|
|
}
|
2022-02-02 16:09:36 +00:00
|
|
|
|
|
|
|
#[cfg(feature = "dark-light")]
|
|
|
|
fn prefer_dark_mode() -> Option<bool> {
|
|
|
|
match dark_light::detect() {
|
|
|
|
dark_light::Mode::Dark => Some(true),
|
|
|
|
dark_light::Mode::Light => Some(false),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(feature = "dark-light"))]
|
|
|
|
fn prefer_dark_mode() -> Option<bool> {
|
|
|
|
None
|
|
|
|
}
|