* Wayland: add persistent file drop guidance (#386) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f77709e9-6475-4953-ab57-b78347b4499d * Fix Wayland file-drop guidance review Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e5e4c3e6-e406-4010-b05d-5042f9f4e4fc * fix(app): pass no dismissal preference for portable notice Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e5e4c3e6-e406-4010-b05d-5042f9f4e4fc * Pass no dismissal preference for the prefs save-failure notice Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * notices::post takes the dismiss preference: pass None for the GPU retry notice (#434) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Copilot-Session: f77709e9-6475-4953-ab57-b78347b4499d Copilot-Session: e5e4c3e6-e406-4010-b05d-5042f9f4e4fc
This commit is contained in:
parent
50d52a0848
commit
047aeb747a
|
|
@ -184,6 +184,12 @@ fn main() -> eframe::Result {
|
|||
let automation = control.as_ref().map(|(_, _, workspace)| workspace.clone());
|
||||
let mut services = services::native(automation);
|
||||
services.preset_store = presets;
|
||||
#[cfg(target_os = "linux")]
|
||||
let display = tablet::DisplayKind::of(cc);
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
services.is_wayland = display == Some(tablet::DisplayKind::Wayland);
|
||||
}
|
||||
let mut app = PhotocraftApp::new(Session::new(), services);
|
||||
app.integrated_titlebar = cfg!(target_os = "macos");
|
||||
// Long commands and file opens run as background jobs with progress and Cancel (#210).
|
||||
|
|
@ -264,12 +270,12 @@ fn main() -> eframe::Result {
|
|||
// and the X11 reader write into this feed.
|
||||
app.stylus.feed = stylus_feed;
|
||||
#[cfg(target_os = "linux")]
|
||||
tablet::spawn_x11(&app.stylus.feed, tablet::DisplayKind::of(cc));
|
||||
tablet::spawn_x11(&app.stylus.feed, display);
|
||||
// Paths on the command line (Linux/Windows file associations, `photocraft a.psd`).
|
||||
app.open_paths(&files);
|
||||
// Portable marker found but its data folder isn't writable (#228): say where settings went.
|
||||
if let Some(w) = &app_dirs::current().warning {
|
||||
photocraft_ui_egui::notices::post(&mut app, "Portable mode is off", vec![w.clone()], false);
|
||||
photocraft_ui_egui::notices::post(&mut app, "Portable mode is off", vec![w.clone()], false, None);
|
||||
}
|
||||
Ok(Box::new(app))
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -191,6 +191,7 @@ pub fn native(automation: Option<photocraft_automation::AuthorizedWorkspace>) ->
|
|||
os_events: None,
|
||||
// Set by main, which starts loading the store before the window opens.
|
||||
preset_store: None,
|
||||
is_wayland: false,
|
||||
..recovery_services(recovery_dir())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
//! ```
|
||||
//!
|
||||
//! `--safe-gpu` draws the canvas on the CPU path, like the app's `--safe-gpu` launch.
|
||||
//! `--wayland-notice` previews the native file drag-and-drop guidance shown in Wayland sessions.
|
||||
//!
|
||||
//! `--monitor 1366x768 --window-top 31` simulates the display the window is on (in points) and
|
||||
//! where its content starts on it, e.g. a window running under a Windows taskbar.
|
||||
|
|
@ -29,6 +30,7 @@ fn main() {
|
|||
.and_then(|s| s.split_once('x').and_then(|(a, b)| Some((a.parse::<f32>().ok()?, b.parse::<f32>().ok()?))))
|
||||
.unwrap_or((1440.0, 900.0));
|
||||
let scale: f32 = arg(&args, "--scale").and_then(|s| s.parse().ok()).unwrap_or(2.0);
|
||||
let wayland_notice = args.iter().any(|a| a == "--wayland-notice");
|
||||
let script: Vec<(String, Value)> =
|
||||
arg(&args, "--script").map(|s| serde_json::from_str::<Vec<(String, Value)>>(&s).expect("--script must be [[method, params], …]")).unwrap_or_default();
|
||||
|
||||
|
|
@ -53,6 +55,8 @@ fn main() {
|
|||
let mut harness =
|
||||
egui_kittest::Harness::builder().with_size(egui::vec2(w, h)).with_pixels_per_point(scale).with_max_steps(64).wgpu().build_eframe(move |cc| {
|
||||
PhotocraftApp::setup_context(&cc.egui_ctx, Default::default());
|
||||
let mut services = services;
|
||||
services.is_wayland = wayland_notice;
|
||||
let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), services);
|
||||
app.background_jobs = background_jobs;
|
||||
// `--safe-gpu`: the CPU canvas, as the desktop app's `--safe-gpu` launch.
|
||||
|
|
|
|||
|
|
@ -1181,7 +1181,7 @@ fn start_screen(app: &mut PhotocraftApp, ui: &mut egui::Ui) {
|
|||
});
|
||||
ui.add_space(22.0);
|
||||
ui.horizontal(|ui| {
|
||||
let msg = tl!("Drop an image or PSD anywhere to open it.");
|
||||
let msg = start_screen_drop_hint(app.services.is_wayland);
|
||||
let g = ui.painter().layout_no_wrap(msg.into(), egui::FontId::proportional(12.5), t.text_faint);
|
||||
ui.add_space(((card.width() - g.size().x - 24.0) / 2.0).max(0.0));
|
||||
let (r, _) = ui.allocate_exact_size(egui::vec2(18.0, 18.0), Sense::hover());
|
||||
|
|
@ -1200,6 +1200,10 @@ fn start_screen(app: &mut PhotocraftApp, ui: &mut egui::Ui) {
|
|||
});
|
||||
}
|
||||
|
||||
fn start_screen_drop_hint(is_wayland: bool) -> &'static str {
|
||||
if is_wayland { tl!("Use File › Open to open an image.") } else { tl!("Drop an image or PSD anywhere to open it.") }
|
||||
}
|
||||
|
||||
/// Recent files listed on the Home screen.
|
||||
const HOME_RECENT: usize = 6;
|
||||
/// Height of one Home-screen recent-file row.
|
||||
|
|
@ -2496,6 +2500,11 @@ fn hex(c: [f32; 4]) -> String {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn wayland_start_screen_hint_does_not_claim_file_drop_works() {
|
||||
assert_ne!(start_screen_drop_hint(true), start_screen_drop_hint(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pointer_moves_are_bounded_by_the_press_and_release() {
|
||||
use egui::{Event, PointerButton, pos2};
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ fn dropped_files_open_with_path_and_recent() {
|
|||
fn notices_are_capped_and_dismissable_state_round_trips() {
|
||||
let (mut app, _) = app_with(None, None);
|
||||
for i in 0..10 {
|
||||
notices::post(&mut app, format!("n{i}"), vec![], false);
|
||||
notices::post(&mut app, format!("n{i}"), vec![], false, None);
|
||||
}
|
||||
assert_eq!(app.ui.notices.len(), notices::MAX_NOTICES);
|
||||
assert_eq!(app.ui.notices.last().map(|n| n.title.as_str()), Some("n9"));
|
||||
|
|
@ -238,7 +238,7 @@ fn notices_are_capped_and_dismissable_state_round_trips() {
|
|||
#[test]
|
||||
fn notices_render_without_panicking() {
|
||||
let (mut app, _) = app_with(None, None);
|
||||
notices::post(&mut app, "Opened a.psd with 9 warnings", (0..9).map(|i| format!("warning {i}")).collect(), false);
|
||||
notices::post(&mut app, "Opened a.psd with 9 warnings", (0..9).map(|i| format!("warning {i}")).collect(), false, None);
|
||||
notices::error(&mut app, "Couldn't open b.psd: not an image".into());
|
||||
let ctx = egui::Context::default();
|
||||
let mut out = ctx.run_ui(Default::default(), |ui| notices::show(&mut app, ui.ctx()));
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ fn choose_recovery(app: &mut PhotocraftApp, retry: bool) -> Result<(), String> {
|
|||
"GPU retry scheduled",
|
||||
vec!["Save your work and restart PhotoCraft to retry GPU acceleration. CPU rendering remains active for this session.".into()],
|
||||
false,
|
||||
None,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -976,9 +976,12 @@
|
|||
Drag to move the active shape's path or the Work Path Tažením přesunete cestu aktivního tvaru nebo pracovní cestu
|
||||
Draw with the Pen tool (P) or make a work path from a selection. Kreslete nástrojem pero (P) nebo vytvořte pracovní cestu z výběru.
|
||||
Drop an image or PSD anywhere to open it. Přetáhněte sem obrázek nebo PSD a otevře se.
|
||||
Native file drag-and-drop is unavailable Nativní přetahování souborů ve Waylandu není dostupné
|
||||
Native file drag-and-drop is not supported on Wayland yet. Use File › Open, or run PhotoCraft under XWayland with `WAYLAND_DISPLAY= photocraft`. Nativní přetahování souborů ve Waylandu zatím není podporováno. Použijte Soubor › Otevřít nebo spusťte PhotoCraft v XWayland s `WAYLAND_DISPLAY= photocraft`.
|
||||
Duplicate Layers… Duplikovat vrstvy…
|
||||
Duplicate style Duplikovat styl
|
||||
Edit Toolbar… Upravit panel nástrojů…
|
||||
Use File › Open to open an image. Obrázek otevřete přes Soubor › Otevřít.
|
||||
Edit in Quick Mask Mode (Q) Upravit v režimu rychlé masky (Q)
|
||||
Edit in Standard Mode (Q) Upravit ve standardním režimu (Q)
|
||||
Effects Efekty
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -961,9 +961,12 @@
|
|||
Drag to move the active shape's path or the Work Path Arrastre para mover el trazado de la forma activa o el Trazado de trabajo
|
||||
Draw with the Pen tool (P) or make a work path from a selection. Dibuja con la herramienta Pluma (P) o crea un trazado de trabajo a partir de una selección.
|
||||
Drop an image or PSD anywhere to open it. Suelte una imagen o un PSD en cualquier lugar para abrirlo.
|
||||
Native file drag-and-drop is unavailable El arrastrar y soltar archivos de forma nativa no está disponible en Wayland
|
||||
Native file drag-and-drop is not supported on Wayland yet. Use File › Open, or run PhotoCraft under XWayland with `WAYLAND_DISPLAY= photocraft`. El arrastrar y soltar archivos de forma nativa aún no está disponible en Wayland. Usa Archivo › Abrir o ejecuta PhotoCraft bajo XWayland con `WAYLAND_DISPLAY= photocraft`.
|
||||
Duplicate Layers… Duplicar capas…
|
||||
Duplicate style Duplicar estilo
|
||||
Edit Toolbar… Editar barra de herramientas…
|
||||
Use File › Open to open an image. Usa Archivo › Abrir para abrir una imagen.
|
||||
Edit in Quick Mask Mode (Q) Editar en modo de máscara rápida (Q)
|
||||
Edit in Standard Mode (Q) Editar en modo estándar (Q)
|
||||
Effects Efectos
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 4 and column 47.
|
|
|
@ -966,9 +966,12 @@
|
|||
Drag to move the active shape's path or the Work Path ドラッグして、アクティブなシェイプのパスまたは作業用パスを移動
|
||||
Draw with the Pen tool (P) or make a work path from a selection. ペンツール(P)で描くか、選択範囲から作業用パスを作成します。
|
||||
Drop an image or PSD anywhere to open it. 画像や PSD をここにドロップすると開きます。
|
||||
Native file drag-and-drop is unavailable Wayland ではネイティブのファイルドラッグ&ドロップを利用できません
|
||||
Native file drag-and-drop is not supported on Wayland yet. Use File › Open, or run PhotoCraft under XWayland with `WAYLAND_DISPLAY= photocraft`. Wayland ではネイティブのファイルドラッグ&ドロップはまだ利用できません。[ファイル › 開く] を使うか、`WAYLAND_DISPLAY= photocraft` を指定して PhotoCraft を XWayland 上で起動してください。
|
||||
Duplicate Layers… レイヤーを複製…
|
||||
Duplicate style スタイルを複製
|
||||
Edit Toolbar… ツールバーを編集…
|
||||
Use File › Open to open an image. 画像を開くには [ファイル › 開く] を使ってください。
|
||||
Edit in Quick Mask Mode (Q) クイックマスクモードで編集(Q)
|
||||
Edit in Standard Mode (Q) 標準モードで編集(Q)
|
||||
Effects 効果
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -1368,6 +1368,9 @@
|
|||
BLANK DOCUMENT PRESETS ({n}) НАБОРЫ ПУСТЫХ ДОКУМЕНТОВ ({n})
|
||||
Create a new document or open an existing file. Создайте новый документ или откройте существующий файл.
|
||||
Drop an image or PSD anywhere to open it. Перетащите изображение или PSD в любое место окна, чтобы открыть.
|
||||
Use File › Open to open an image. Чтобы открыть изображение, выберите Файл › Открыть.
|
||||
Native file drag-and-drop is unavailable Нативное перетаскивание файлов в Wayland недоступно
|
||||
Native file drag-and-drop is not supported on Wayland yet. Use File › Open, or run PhotoCraft under XWayland with `WAYLAND_DISPLAY= photocraft`. Нативное перетаскивание файлов в Wayland пока не поддерживается. Откройте файл через Файл › Открыть или запустите PhotoCraft в XWayland с `WAYLAND_DISPLAY= photocraft`.
|
||||
Photo Фото
|
||||
Print Печать
|
||||
Art & Illustration Искусство и иллюстрация
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -965,9 +965,12 @@
|
|||
Drag to move the active shape's path or the Work Path 拖动以移动当前形状的路径或工作路径
|
||||
Draw with the Pen tool (P) or make a work path from a selection. 使用钢笔工具(P)绘制,或从选区创建工作路径。
|
||||
Drop an image or PSD anywhere to open it. 将图像或 PSD 拖放到任意位置即可打开。
|
||||
Native file drag-and-drop is unavailable Wayland 上不支持原生文件拖放
|
||||
Native file drag-and-drop is not supported on Wayland yet. Use File › Open, or run PhotoCraft under XWayland with `WAYLAND_DISPLAY= photocraft`. Wayland 尚不支持原生文件拖放。请使用“文件 › 打开”,或通过 `WAYLAND_DISPLAY= photocraft` 在 XWayland 下运行 PhotoCraft。
|
||||
Duplicate Layers… 复制图层…
|
||||
Duplicate style 复制样式
|
||||
Edit Toolbar… 编辑工具栏…
|
||||
Use File › Open to open an image. 使用“文件 › 打开”打开图像。
|
||||
Edit in Quick Mask Mode (Q) 在快速蒙版模式下编辑 (Q)
|
||||
Edit in Standard Mode (Q) 在标准模式下编辑 (Q)
|
||||
Effects 效果
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 13.
|
|
|
@ -967,9 +967,12 @@
|
|||
Drag to move the active shape's path or the Work Path 拖曳以移動作用中形狀的路徑或工作路徑
|
||||
Draw with the Pen tool (P) or make a work path from a selection. 使用筆型工具 (P) 繪製,或從選取範圍建立工作路徑。
|
||||
Drop an image or PSD anywhere to open it. 將影像或 PSD 拖放到任何位置即可開啟。
|
||||
Native file drag-and-drop is unavailable Wayland 不支援原生檔案拖放
|
||||
Native file drag-and-drop is not supported on Wayland yet. Use File › Open, or run PhotoCraft under XWayland with `WAYLAND_DISPLAY= photocraft`. Wayland 尚未支援原生檔案拖放。請使用「檔案 › 開啟」,或透過 `WAYLAND_DISPLAY= photocraft` 在 XWayland 下執行 PhotoCraft。
|
||||
Duplicate Layers… 複製圖層…
|
||||
Duplicate style 複製樣式
|
||||
Edit Toolbar… 編輯工具列…
|
||||
Use File › Open to open an image. 使用「檔案 › 開啟」開啟影像。
|
||||
Edit in Quick Mask Mode (Q) 在快速遮色片模式下編輯 (Q)
|
||||
Edit in Standard Mode (Q) 在標準模式下編輯 (Q)
|
||||
Effects 效果
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -223,6 +223,8 @@ pub struct Services {
|
|||
/// the web (see `prefs_ui`).
|
||||
pub load_prefs: Option<LoadTextFn>,
|
||||
pub save_prefs: Option<SaveTextFn>,
|
||||
/// The native window is connected directly to a Wayland compositor.
|
||||
pub is_wayland: bool,
|
||||
/// Crash-recovery autosave (Preferences › File Handling) and recovery at launch.
|
||||
pub autosave: Option<AutosaveFn>,
|
||||
pub discard_autosave: Option<DiscardAutosaveFn>,
|
||||
|
|
@ -451,6 +453,7 @@ impl PhotocraftApp {
|
|||
};
|
||||
// Saved preferences (and recovered documents) are in place before the first frame.
|
||||
prefs_ui::load(&mut app);
|
||||
notices::wayland_file_drop_guidance(&mut app);
|
||||
// File › Scripts › Script Events Manager: "Start Application".
|
||||
photocraft_engine::automate_cmds::fire_event(&mut app.session, "startApplication");
|
||||
app
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::PhotocraftApp;
|
||||
|
||||
const WAYLAND_FILE_DROP_DISMISSED: &str = "ui.waylandFileDropGuidanceDismissed";
|
||||
/// At most this many notices are kept; older ones drop off.
|
||||
pub const MAX_NOTICES: usize = 3;
|
||||
/// Lines shown per notice before "…and N more".
|
||||
|
|
@ -21,19 +22,55 @@ pub struct Notice {
|
|||
/// An error (warning colour) rather than an informational notice.
|
||||
#[serde(default)]
|
||||
pub error: bool,
|
||||
/// Preference key to set when this notice is dismissed.
|
||||
#[serde(default)]
|
||||
pub dismiss_pref: Option<String>,
|
||||
}
|
||||
|
||||
/// Show a notice (newest last); returns its id.
|
||||
pub fn post(app: &mut PhotocraftApp, title: impl Into<String>, lines: Vec<String>, error: bool) -> u64 {
|
||||
pub fn post(app: &mut PhotocraftApp, title: impl Into<String>, lines: Vec<String>, error: bool, dismiss_pref: Option<&str>) -> u64 {
|
||||
let id = app.ui.alloc_id();
|
||||
app.ui.notices.push(Notice { id, title: title.into(), lines, error });
|
||||
let n = app.ui.notices.len();
|
||||
if n > MAX_NOTICES {
|
||||
app.ui.notices.drain(..n - MAX_NOTICES);
|
||||
}
|
||||
app.ui.notices.push(Notice { id, title: title.into(), lines, error, dismiss_pref: dismiss_pref.map(str::to_owned) });
|
||||
cap_notices(app);
|
||||
id
|
||||
}
|
||||
|
||||
fn cap_notices(app: &mut PhotocraftApp) {
|
||||
while app.ui.notices.len() > MAX_NOTICES {
|
||||
let oldest_temporary = app.ui.notices.iter().position(|notice| notice.dismiss_pref.is_none()).unwrap_or(0);
|
||||
app.ui.notices.remove(oldest_temporary);
|
||||
}
|
||||
}
|
||||
|
||||
/// Show the Wayland-specific fallback guidance unless the user dismissed it in preferences.
|
||||
pub fn wayland_file_drop_guidance(app: &mut PhotocraftApp) {
|
||||
if !app.services.is_wayland
|
||||
|| app.session.prefs().dialogs.get(WAYLAND_FILE_DROP_DISMISSED).and_then(serde_json::Value::as_bool) == Some(true)
|
||||
|| app.ui.notices.iter().any(|notice| notice.dismiss_pref.as_deref() == Some(WAYLAND_FILE_DROP_DISMISSED))
|
||||
{
|
||||
return;
|
||||
}
|
||||
post(
|
||||
app,
|
||||
tl!("Native file drag-and-drop is unavailable"),
|
||||
vec![
|
||||
tl!("Native file drag-and-drop is not supported on Wayland yet. Use File › Open, or run PhotoCraft under XWayland with `WAYLAND_DISPLAY= photocraft`.").into(),
|
||||
],
|
||||
false,
|
||||
Some(WAYLAND_FILE_DROP_DISMISSED),
|
||||
);
|
||||
}
|
||||
|
||||
fn dismiss(app: &mut PhotocraftApp, id: u64) {
|
||||
let dismiss_pref = app.ui.notices.iter().find(|notice| notice.id == id).and_then(|notice| notice.dismiss_pref.clone());
|
||||
app.ui.notices.retain(|notice| notice.id != id);
|
||||
if let Some(key) = dismiss_pref {
|
||||
app.session.prefs.edit(|prefs| {
|
||||
prefs.dialogs.insert(key, serde_json::Value::Bool(true));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Report import/export `warnings` for the file operation `what` (e.g. "Opened a.psd"): the status
|
||||
/// bar says how many there were, and a notice lists them. Nothing happens when there are none.
|
||||
pub fn io_warnings(app: &mut PhotocraftApp, what: &str, warnings: &[String]) {
|
||||
|
|
@ -41,7 +78,7 @@ pub fn io_warnings(app: &mut PhotocraftApp, what: &str, warnings: &[String]) {
|
|||
let n = warnings.len();
|
||||
app.ui.status = if n == 1 { format!("{what}: {first}") } else { format!("{what} with {n} warnings: {first} …") };
|
||||
app.ui.status_error = true;
|
||||
post(app, format!("{what} with {n} warning{}", if n == 1 { "" } else { "s" }), warnings.to_vec(), false);
|
||||
post(app, format!("{what} with {n} warning{}", if n == 1 { "" } else { "s" }), warnings.to_vec(), false, None);
|
||||
}
|
||||
|
||||
/// Report a failed file operation: the status bar shows it as an error and a notice keeps it on
|
||||
|
|
@ -49,7 +86,7 @@ pub fn io_warnings(app: &mut PhotocraftApp, what: &str, warnings: &[String]) {
|
|||
pub fn error(app: &mut PhotocraftApp, message: String) {
|
||||
app.ui.status = message.clone();
|
||||
app.ui.status_error = true;
|
||||
post(app, message, Vec::new(), true);
|
||||
post(app, message, Vec::new(), true, None);
|
||||
}
|
||||
|
||||
/// Draw the notices; each has a close button.
|
||||
|
|
@ -58,7 +95,7 @@ pub fn show(app: &mut PhotocraftApp, ctx: &egui::Context) {
|
|||
return;
|
||||
}
|
||||
let t = crate::theme::Tokens::get(ctx);
|
||||
let mut dismiss = None;
|
||||
let mut dismiss_id = None;
|
||||
// Clear the status bar (~24 px) and leave the dock's edge some air.
|
||||
egui::Area::new(egui::Id::new("photocraft-notices"))
|
||||
.order(egui::Order::Foreground)
|
||||
|
|
@ -80,7 +117,7 @@ pub fn show(app: &mut PhotocraftApp, ctx: &egui::Context) {
|
|||
ui.add(egui::Label::new(title).wrap());
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| {
|
||||
if ui.add(egui::Button::new(egui::RichText::new("×").color(t.text_dim)).frame(false)).on_hover_text(tl!("Dismiss")).clicked() {
|
||||
dismiss = Some(n.id);
|
||||
dismiss_id = Some(n.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -94,7 +131,71 @@ pub fn show(app: &mut PhotocraftApp, ctx: &egui::Context) {
|
|||
ui.add_space(6.0);
|
||||
}
|
||||
});
|
||||
if let Some(id) = dismiss {
|
||||
app.ui.notices.retain(|n| n.id != id);
|
||||
if let Some(id) = dismiss_id {
|
||||
dismiss(app, id);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{PhotocraftApp, Services};
|
||||
use photocraft_engine::Session;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[test]
|
||||
fn wayland_guidance_is_only_shown_in_wayland_sessions() {
|
||||
let mut app = PhotocraftApp::new(Session::new(), Services { is_wayland: true, ..Default::default() });
|
||||
assert_eq!(app.ui.notices.len(), 1);
|
||||
assert_eq!(app.ui.notices[0].title, "Native file drag-and-drop is unavailable");
|
||||
let guidance = app.ui.notices[0].lines.join(" ");
|
||||
assert!(guidance.contains("not supported on Wayland yet"));
|
||||
assert!(guidance.contains("File › Open"));
|
||||
assert!(!guidance.contains("Ctrl+V"));
|
||||
assert!(guidance.contains("XWayland"));
|
||||
assert!(guidance.contains("WAYLAND_DISPLAY= photocraft"));
|
||||
assert_eq!(app.ui.notices[0].dismiss_pref.as_deref(), Some(WAYLAND_FILE_DROP_DISMISSED));
|
||||
for i in 0..MAX_NOTICES {
|
||||
post(&mut app, format!("Transient {i}"), Vec::new(), false, None);
|
||||
}
|
||||
assert_eq!(app.ui.notices.len(), MAX_NOTICES);
|
||||
assert!(app.ui.notices.iter().any(|notice| notice.dismiss_pref.is_some()));
|
||||
|
||||
let app = PhotocraftApp::new(Session::new(), Services::default());
|
||||
assert!(app.ui.notices.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dismissing_wayland_guidance_persists_across_launches() {
|
||||
let saved = Arc::new(Mutex::new(None::<String>));
|
||||
let load_store = saved.clone();
|
||||
let save_store = saved.clone();
|
||||
let services = Services {
|
||||
is_wayland: true,
|
||||
load_prefs: Some(Box::new(move || load_store.lock().unwrap_or_else(|e| e.into_inner()).clone())),
|
||||
save_prefs: Some(Box::new(move |text| {
|
||||
*save_store.lock().unwrap_or_else(|e| e.into_inner()) = Some(text.to_owned());
|
||||
Ok(())
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
let mut app = PhotocraftApp::new(Session::new(), services);
|
||||
let id = app.ui.notices[0].id;
|
||||
dismiss(&mut app, id);
|
||||
crate::prefs_ui::tick(&mut app, &egui::Context::default());
|
||||
|
||||
let services =
|
||||
Services { is_wayland: true, load_prefs: Some(Box::new(move || saved.lock().unwrap_or_else(|e| e.into_inner()).clone())), ..Default::default() };
|
||||
let app = PhotocraftApp::new(Session::new(), services);
|
||||
assert!(app.ui.notices.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dismissing_a_notice_writes_its_own_preference_key() {
|
||||
let mut app = PhotocraftApp::new(Session::new(), Services::default());
|
||||
post(&mut app, "Dismissible", Vec::new(), false, Some("ui.testNoticeDismissed"));
|
||||
let id = app.ui.notices[0].id;
|
||||
dismiss(&mut app, id);
|
||||
assert_eq!(app.session.prefs().dialogs.get("ui.testNoticeDismissed").and_then(serde_json::Value::as_bool), Some(true));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ fn persist(app: &mut PhotocraftApp, now: f64) -> Option<f64> {
|
|||
app.ui.status_error = true;
|
||||
} else if failures == SAVE_NOTICE_AFTER {
|
||||
let lines = vec![e, "PhotoCraft keeps retrying; until a save succeeds, preference changes are lost when it closes.".into()];
|
||||
let id = crate::notices::post(app, "Preferences can't be saved", lines, true);
|
||||
let id = crate::notices::post(app, "Preferences can't be saved", lines, true, None);
|
||||
app.prefs_rt.save_retry.notice = Some(id);
|
||||
}
|
||||
Some(delay)
|
||||
|
|
|
|||
Loading…
Reference in New Issue