Files
vectorcraft/apps/vectorcraft/src/app.rs
T

188 lines
8.9 KiB
Rust

#[cfg(all(target_os = "macos", feature = "accessibility", feature = "wgpu"))]
use super::gpu;
use super::prefs::{save_prefs, save_prefs_soon};
#[cfg(not(target_os = "macos"))]
use super::single_instance;
#[cfg(all(windows, not(target_vendor = "win7")))]
use super::windows_stylus;
use super::{App, VectorcraftApp, file_access, window};
#[cfg(target_os = "macos")]
use super::{mac_menu, mac_window, open_documents};
/// The parts of [`App::raw_input_hook`] that every window needs, for the OS windows of floating
/// panels: a click or Escape that closes the macOS menu bar's open menu, and the pen barrel
/// button's secondary click on Windows. The control channel's input and the native menu's keys
/// go to the main window only.
#[cfg(target_os = "macos")]
pub(crate) fn native_input_hook(app: &VectorcraftApp) -> Option<vectorcraft_ui_egui::NativeInputHook> {
app.services.native_menu.is_some().then(|| std::sync::Arc::new(mac_menu::raw_input_hook) as vectorcraft_ui_egui::NativeInputHook)
}
#[cfg(windows)]
pub(crate) fn native_input_hook(_: &VectorcraftApp) -> Option<vectorcraft_ui_egui::NativeInputHook> {
Some(std::sync::Arc::new(windows_stylus::raw_input_hook))
}
#[cfg(not(any(target_os = "macos", windows)))]
pub(crate) fn native_input_hook(_: &VectorcraftApp) -> Option<vectorcraft_ui_egui::NativeInputHook> {
None
}
impl eframe::App for App {
// Each frame runs unconfined: it is the person at the keyboard's. The app confines what the
// control channel asks for, and the frames carrying input it injected, to the automation roots
// (`VectorcraftApp::with_automation_roots`), which are in force elsewhere in the process.
fn logic(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
file_access::unconfined(|| self.logic_frame(ctx, frame));
self.app.finish_ime_logic(ctx);
}
/// Restore the borderless caption, apply platform menu and pen input, and hand the frame to the
/// app's raw-input hook while the keyboard user is unconfined.
fn raw_input_hook(&mut self, ctx: &egui::Context, raw: &mut egui::RawInput) {
// winit gives the window its caption back when it changes the window's flags (maximize,
// restore, …): take it off again before the frame is laid out (#1758).
if let Some(window_frame) = self.window_frame.as_mut() {
window_frame();
}
#[cfg(target_os = "macos")]
if self.app.services.native_menu.is_some() {
mac_menu::raw_input_hook(raw);
}
// A pen contact with the barrel button held presses the secondary button (Windows Ink).
#[cfg(windows)]
windows_stylus::raw_input_hook(raw);
// The characters Alt + keypad codes typed (#5108).
#[cfg(windows)]
super::windows_alt_codes::raw_input_hook(raw);
file_access::unconfined(|| self.app.raw_input_hook(ctx, raw));
// A mouse-up that never reached the window (#4326) is delivered once AppKit reports
// that no mouse button is down.
#[cfg(target_os = "macos")]
self.app.release_lost_buttons(ctx, raw, objc2_app_kit::NSEvent::pressedMouseButtons() != 0);
}
#[cfg_attr(not(target_os = "macos"), allow(unused_variables))]
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
#[cfg(target_os = "macos")]
{
let bar = vectorcraft_ui_egui::chrome::APP_BAR_HEIGHT;
let inset = mac_window::center_buttons(frame, ui.ctx().zoom_factor(), bar);
self.app.titlebar_inset = inset.unwrap_or(mac_window::DEFAULT_INSET);
}
// macOS converts the frame from sRGB to the display's profile (#2249).
#[cfg(all(target_os = "macos", feature = "wgpu"))]
super::mac_color_space::tag(ui);
if let Some(on) = self.host_caps_lock.read_transition() {
self.app.set_caps_lock(on);
}
file_access::unconfined(|| self.app.ui(ui));
// Preferences are written when confirmed, not only at exit: a session ended any other way
// (the system shutting down, the process ended or crashing) used to lose them (#1747).
if self.app.take_prefs_changed() {
file_access::unconfined(|| save_prefs(&self.app));
} else {
// The workspace, panels and other UI settings change without the dialog (#4625).
file_access::unconfined(|| save_prefs_soon(&self.app));
}
// Not while a file dialog's sheet has the keyboard: its name field's composition would go.
#[cfg(target_os = "macos")]
if !self.app.file_dialog_open() && self.app.take_ime_discard() {
discard_marked_text();
}
}
fn on_exit(&mut self) {
file_access::unconfined(|| save_prefs(&self.app));
end_before_teardown(self.frames);
}
}
impl App {
fn logic_frame(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
self.frames = ctx.cumulative_frame_nr();
if let Some(why) = self.graphics_loss.take() {
// eframe can't give a window a new device: the user saves and starts again.
self.graphics_lost = self.app.graphics_lost(&why);
self.app.status(if self.graphics_lost {
"The graphics device was lost: unsaved changes are kept for Data Recovery. Save your documents and restart VectorCraft"
} else {
"The graphics device was lost: save your documents and restart VectorCraft"
});
}
if self.graphics_lost && ctx.input(|i| i.viewport().close_requested()) {
// The window can't show the Save Changes question: it closes, and the next launch
// offers the changes back.
vectorcraft_ui_egui::background::wait_all(&mut self.app);
return;
}
// Files from the Finder and the Dock (macOS) or from later launches (#4892) wait while a file
// dialog is open: what asked for it runs again on its document when it answers.
if !self.app.file_dialog_open() {
#[cfg(target_os = "macos")]
let files = open_documents::take();
#[cfg(not(target_os = "macos"))]
let files = single_instance::take();
if !files.is_empty() {
bring_window_forward(ctx);
}
open_files(&mut self.app, files);
}
self.app.tick(ctx);
if self.pending_maximize {
if let Some(w) = &self.winit_window {
self.pending_maximize = window::apply_pending_maximize(w, true, &mut self.maximize_tries);
} else {
self.pending_maximize = false;
}
}
let host_maximized = self.winit_window.as_ref().map(|w| w.is_maximized());
window::track(ctx, &mut self.app.ui.window, host_maximized);
if self.app.ui.status == "quit" {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
}
}
/// On macOS with accessibility, end the process once the app has saved what it keeps on quitting,
/// before eframe tears the window down (#661). AccessKit then gives the window's content view back
/// its own class, which AppKit's Touch Bar support was observing it under, and AppKit aborts the
/// app as it quits on Macs with a Touch Bar. Nothing after the window is needed on a normal quit.
/// Not while the window is still starting up (`frames` below [`gpu::STARTUP_FRAMES`]): a graphics
/// failure then starts the app again on another adapter (#651).
#[allow(unused_variables)]
fn end_before_teardown(frames: u64) {
#[cfg(all(target_os = "macos", feature = "accessibility", feature = "wgpu"))]
if frames >= gpu::STARTUP_FRAMES {
log::info!("quitting");
log::logger().flush();
std::process::exit(0);
}
}
/// Show the window when files are opened from the Finder or the Dock, or handed over by a later
/// launch (#4892): a minimized window comes back and the app takes focus, as a document-based app
/// does (#3769).
pub(crate) fn bring_window_forward(ctx: &egui::Context) {
ctx.send_viewport_cmd(egui::ViewportCommand::Minimized(false));
ctx.send_viewport_cmd(egui::ViewportCommand::Focus);
}
/// Open files handed to the app (command line, macOS Finder and Dock, later launches) as documents. A file that
/// can't be opened is reported to the user and on stderr; the others still open.
pub(crate) fn open_files(app: &mut VectorcraftApp, files: Vec<String>) {
for f in files {
if let Err(e) = vectorcraft_ui_egui::io::open_reporting(app, &f) {
eprintln!("vectorcraft: {f}: {e}");
}
}
}
/// Tell the macOS input method to drop its composition (the Type tool kept the marked text as
/// typed). winit's IME toggle only clears its own copy, so the IME would type it again.
#[cfg(target_os = "macos")]
fn discard_marked_text() {
if let Some(mtm) = objc2::MainThreadMarker::new()
&& let Some(ic) = objc2_app_kit::NSTextInputContext::currentInputContext(mtm)
{
ic.discardMarkedText();
}
}