mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
Issue #8: prefer the integrated GPU, and the save prompt answers to the keyboard
- On hybrid-graphics laptops wgpu picked the discrete GPU (egui-wgpu defaults to HighPerformance). NVIDIA's can lose or corrupt its memory across suspend and screen lock, which matches the illegible window in #8. PrintCraft now asks for the low-power adapter unless WGPU_POWER_PREF is set; one-GPU machines are unaffected, and it saves battery. - "Save changes?" had no keyboard: Enter saves, Escape cancels, and Don't save takes Cmd/Ctrl+D (macOS) or Alt+D / Alt+N (the mnemonics the reporter tried). Its keys are read before the canvas (which took Escape), and no other shortcut runs underneath it (Cmd+D opened Document properties). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a5d830bf25
commit
27f7ef96fe
@@ -71,7 +71,8 @@ fn main() -> eframe::Result {
|
||||
}
|
||||
// eframe would otherwise derive the settings folder from the app id: keep it under "PrintCraft".
|
||||
let persistence_path = eframe::storage_dir("PrintCraft").map(|d| d.join("app.ron"));
|
||||
let native = eframe::NativeOptions { viewport, persistence_path, ..Default::default() };
|
||||
let mut native = eframe::NativeOptions { viewport, persistence_path, ..Default::default() };
|
||||
prefer_integrated_gpu(&mut native);
|
||||
eframe::run_native(
|
||||
"PrintCraft",
|
||||
native,
|
||||
@@ -125,3 +126,16 @@ fn write_control_file(path: &str, port: u16, token: &str) -> std::io::Result<()>
|
||||
}
|
||||
f.write_all(json.as_bytes())
|
||||
}
|
||||
|
||||
/// Draw on the integrated GPU unless `WGPU_POWER_PREF` says otherwise. A PDF viewer has no use
|
||||
/// for a discrete GPU, and on hybrid-graphics laptops (NVIDIA Optimus) the discrete one can lose
|
||||
/// or corrupt its memory across suspend and screen lock, leaving the window illegible (issue #8).
|
||||
/// It also saves battery. Machines with one GPU are unaffected.
|
||||
fn prefer_integrated_gpu(native: &mut eframe::NativeOptions) {
|
||||
if std::env::var_os("WGPU_POWER_PREF").is_some() {
|
||||
return;
|
||||
}
|
||||
if let eframe::egui_wgpu::WgpuSetup::CreateNew(setup) = &mut native.wgpu_options.wgpu_setup {
|
||||
setup.power_preference = eframe::wgpu::PowerPreference::LowPower;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1252,6 +1252,24 @@ fn draft_changes(app: &PrintCraftApp) -> Option<Vec<Edit>> {
|
||||
Some(edits)
|
||||
}
|
||||
|
||||
/// The "Save changes?" prompt's keys (issue #8), read before anything else can take them: Enter
|
||||
/// saves (the default button) and Escape cancels; Don't save takes ⌘D / Ctrl+D, the macOS
|
||||
/// convention, or Alt+D / Alt+N, the mnemonics Windows and Linux desktops use for it.
|
||||
pub(crate) fn save_prompt_key(ctx: &egui::Context) -> Option<Option<bool>> {
|
||||
use egui::{Key, Modifiers};
|
||||
ctx.input_mut(|i| {
|
||||
if i.consume_key(Modifiers::NONE, Key::Enter) {
|
||||
Some(Some(true))
|
||||
} else if i.consume_key(Modifiers::NONE, Key::Escape) {
|
||||
Some(None)
|
||||
} else if [(Modifiers::COMMAND, Key::D), (Modifiers::ALT, Key::D), (Modifiers::ALT, Key::N)].into_iter().any(|(m, k)| i.consume_key(m, k)) {
|
||||
Some(Some(false))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// "Save changes?" when closing a tab or quitting with unsaved edits.
|
||||
fn save_prompt(app: &mut PrintCraftApp, ctx: &egui::Context) {
|
||||
let Some(req) = app.close_request else { return };
|
||||
|
||||
@@ -1061,6 +1061,15 @@ impl PrintCraftApp {
|
||||
|
||||
fn shortcuts(&mut self, ctx: &egui::Context) {
|
||||
use egui::Key;
|
||||
// "Save changes?" is modal: its keys are its own (⌘D is Don't save there, not Document
|
||||
// properties; Escape cancels it rather than clearing a selection), and nothing may run
|
||||
// underneath it. They are read here, before the canvas can consume them.
|
||||
if self.close_request.is_some() {
|
||||
if let Some(choice) = dialogs::save_prompt_key(ctx) {
|
||||
self.resolve_close(ctx, choice);
|
||||
}
|
||||
return;
|
||||
}
|
||||
self.registry_shortcuts(ctx);
|
||||
if self.full_screen && ctx.input(|i| i.key_pressed(Key::Escape)) {
|
||||
self.set_full_screen(ctx, false);
|
||||
|
||||
@@ -212,6 +212,44 @@ fn closing_a_dirty_tab_can_save_first() {
|
||||
let _ = std::fs::remove_file(out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_save_prompt_answers_to_the_keyboard() {
|
||||
// Issue #8: Enter saves; ⌘D / Ctrl+D, Alt+D and Alt+N don't save; Escape cancels. While the
|
||||
// prompt is open, ⌘D is not Document properties.
|
||||
for (m, key) in [(Modifiers::COMMAND, Key::D), (Modifiers::ALT, Key::D), (Modifiers::ALT, Key::N)] {
|
||||
let mut h = organize(2);
|
||||
h.get_by_label("Rotate clockwise").click();
|
||||
h.run_steps(3);
|
||||
h.key_press_modifiers(Modifiers::COMMAND, Key::W);
|
||||
h.run_steps(3);
|
||||
h.get_by_label_contains("Save changes to “doc.pdf”");
|
||||
h.key_press_modifiers(m, key);
|
||||
h.run_steps(3);
|
||||
assert!(h.state().views.is_empty(), "{m:?}+{key:?} doesn't save and closes");
|
||||
assert!(h.state().dialog.is_none(), "{m:?}+{key:?} ran no command underneath");
|
||||
}
|
||||
let mut h = organize(2);
|
||||
h.get_by_label("Rotate clockwise").click();
|
||||
h.run_steps(3);
|
||||
h.key_press_modifiers(Modifiers::COMMAND, Key::W);
|
||||
h.run_steps(3);
|
||||
h.key_press(Key::Escape);
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().views.len(), 1, "Escape cancels");
|
||||
assert!(h.state().close_request.is_none());
|
||||
|
||||
let out = temp_path("enter-saves.pdf");
|
||||
h.state_mut().save_override = Some(out.to_string_lossy().into_owned());
|
||||
h.key_press_modifiers(Modifiers::COMMAND, Key::W);
|
||||
h.run_steps(3);
|
||||
h.key_press(Key::Enter);
|
||||
h.run_steps(3);
|
||||
assert!(h.state().views.is_empty(), "Enter saves and closes");
|
||||
let saved = printcraft_render::inspect(std::sync::Arc::new(std::fs::read(&out).unwrap()), None).unwrap();
|
||||
assert_eq!(saved.pages[0].rotation, 90);
|
||||
let _ = std::fs::remove_file(out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clean_tabs_close_without_asking() {
|
||||
let mut h = harness(1, |_| {});
|
||||
|
||||
Reference in New Issue
Block a user