Skip to content

Commit fa2167d

Browse files
Desktop: Drag and drop file to open/import functionality (#3035)
* Desktop app add drop file functionality * Add x11 libs to flake * Restructure extension matching to remove nesting --------- Co-authored-by: Dennis Kobert <[email protected]>
1 parent 8d0c9d7 commit fa2167d

File tree

5 files changed

+85
-4
lines changed

5 files changed

+85
-4
lines changed

.nix/flake.nix

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
pkgs = import nixpkgs {
3434
inherit system overlays;
3535
};
36-
36+
3737
rustc-wasm = pkgs.rust-bin.stable.latest.default.override {
3838
targets = [ "wasm32-unknown-unknown" ];
3939
extensions = [ "rust-src" "rust-analyzer" "clippy" "cargo" ];
@@ -75,6 +75,12 @@
7575
vulkan-loader
7676
libraw
7777
libGL
78+
79+
# X11 libraries, not needed on wayland! Remove when x11 is finally dead
80+
libxkbcommon
81+
xorg.libXcursor
82+
xorg.libxcb
83+
xorg.libX11
7884
];
7985

8086
# Development tools that don't need to be in LD_LIBRARY_PATH

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

desktop/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,4 @@ vello = { workspace = true }
3939
derivative = { workspace = true }
4040
rfd = { workspace = true }
4141
open = { workspace = true }
42+
image = { workspace = true }

desktop/src/app.rs

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ use crate::dialogs::dialog_save_graphite_file;
77
use crate::render::GraphicsState;
88
use crate::render::WgpuContext;
99
use graph_craft::wasm_application_io::WasmApplicationIo;
10+
use graphene_std::Color;
11+
use graphene_std::raster::Image;
1012
use graphite_editor::application::Editor;
13+
use graphite_editor::consts::DEFAULT_DOCUMENT_NAME;
1114
use graphite_editor::messages::prelude::*;
15+
use std::fs;
1216
use std::sync::Arc;
1317
use std::sync::mpsc::Sender;
1418
use std::thread;
@@ -75,7 +79,7 @@ impl WinitApp {
7579
String::new()
7680
});
7781
let message = PortfolioMessage::OpenDocumentFile {
78-
document_name: path.file_name().and_then(|s| s.to_str()).unwrap_or("unknown").to_string(),
82+
document_name: path.file_stem().and_then(|s| s.to_str()).unwrap_or("unknown").to_string(),
7983
document_serialized_content: content,
8084
};
8185
let _ = event_loop_proxy.send_event(CustomEvent::DispatchMessage(message.into()));
@@ -264,6 +268,75 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
264268
let Some(event) = self.cef_context.handle_window_event(event) else { return };
265269

266270
match event {
271+
// Currently not supported on wayland see https://github.com/rust-windowing/winit/issues/1881
272+
WindowEvent::DroppedFile(path) => {
273+
let name = path.file_stem().and_then(|s| s.to_str()).map(|s| s.to_string());
274+
let Some(extension) = path.extension().and_then(|s| s.to_str()) else {
275+
tracing::warn!("Unsupported file dropped: {}", path.display());
276+
// Fine to early return since we don't need to do cef work in this case
277+
return;
278+
};
279+
let load_string = |path: &std::path::PathBuf| {
280+
let Ok(content) = fs::read_to_string(path) else {
281+
tracing::error!("Failed to read file: {}", path.display());
282+
return None;
283+
};
284+
285+
if content.is_empty() {
286+
tracing::warn!("Dropped file is empty: {}", path.display());
287+
return None;
288+
}
289+
Some(content)
290+
};
291+
// TODO: Consider moving this logic to the editor so we have one message to load data which is then demultiplexed in the portfolio message handler
292+
match extension {
293+
"graphite" => {
294+
let Some(content) = load_string(&path) else { return };
295+
296+
let message = PortfolioMessage::OpenDocumentFile {
297+
document_name: name.unwrap_or(DEFAULT_DOCUMENT_NAME.to_string()),
298+
document_serialized_content: content,
299+
};
300+
self.dispatch_message(message.into());
301+
}
302+
"svg" => {
303+
let Some(content) = load_string(&path) else { return };
304+
305+
let message = PortfolioMessage::PasteSvg {
306+
name: path.file_stem().map(|s| s.to_string_lossy().to_string()),
307+
svg: content,
308+
mouse: None,
309+
parent_and_insert_index: None,
310+
};
311+
self.dispatch_message(message.into());
312+
}
313+
_ => match image::ImageReader::open(&path) {
314+
Ok(reader) => match reader.decode() {
315+
Ok(image) => {
316+
let width = image.width();
317+
let height = image.height();
318+
// TODO: support loading images with more than 8 bits per channel
319+
let image_data = image.to_rgba8();
320+
let image = Image::<Color>::from_image_data(image_data.as_raw(), width, height);
321+
322+
let message = PortfolioMessage::PasteImage {
323+
name,
324+
image,
325+
mouse: None,
326+
parent_and_insert_index: None,
327+
};
328+
self.dispatch_message(message.into());
329+
}
330+
Err(e) => {
331+
tracing::error!("Failed to decode image: {}: {}", path.display(), e);
332+
}
333+
},
334+
Err(e) => {
335+
tracing::error!("Failed to open image file: {}: {}", path.display(), e);
336+
}
337+
},
338+
}
339+
}
267340
WindowEvent::CloseRequested => {
268341
tracing::info!("The close button was pressed; stopping");
269342
event_loop.exit();

editor/src/messages/portfolio/portfolio_message_handler.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
781781

782782
if create_document {
783783
responses.add(PortfolioMessage::NewDocumentWithName {
784-
name: name.clone().unwrap_or("Untitled Document".into()),
784+
name: name.clone().unwrap_or(DEFAULT_DOCUMENT_NAME.into()),
785785
});
786786
}
787787

@@ -812,7 +812,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
812812

813813
if create_document {
814814
responses.add(PortfolioMessage::NewDocumentWithName {
815-
name: name.clone().unwrap_or("Untitled Document".into()),
815+
name: name.clone().unwrap_or(DEFAULT_DOCUMENT_NAME.into()),
816816
});
817817
}
818818

0 commit comments

Comments
 (0)