diff --git a/.nix/flake.nix b/.nix/flake.nix index d8fb88ed54..d058edc292 100644 --- a/.nix/flake.nix +++ b/.nix/flake.nix @@ -33,7 +33,7 @@ pkgs = import nixpkgs { inherit system overlays; }; - + rustc-wasm = pkgs.rust-bin.stable.latest.default.override { targets = [ "wasm32-unknown-unknown" ]; extensions = [ "rust-src" "rust-analyzer" "clippy" "cargo" ]; @@ -75,6 +75,12 @@ vulkan-loader libraw libGL + + # X11 libraries, not needed on wayland! Remove when x11 is finally dead + libxkbcommon + xorg.libXcursor + xorg.libxcb + xorg.libX11 ]; # Development tools that don't need to be in LD_LIBRARY_PATH diff --git a/Cargo.lock b/Cargo.lock index b34cf496d9..5747646bf3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2113,6 +2113,7 @@ dependencies = [ "graph-craft", "graphene-std", "graphite-editor", + "image", "include_dir", "open", "rfd", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 03768efd38..ee27130810 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -39,3 +39,4 @@ vello = { workspace = true } derivative = { workspace = true } rfd = { workspace = true } open = { workspace = true } +image = { workspace = true } diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 0ec9eaa81e..803a2f0a3a 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -7,8 +7,12 @@ use crate::dialogs::dialog_save_graphite_file; use crate::render::GraphicsState; use crate::render::WgpuContext; use graph_craft::wasm_application_io::WasmApplicationIo; +use graphene_std::Color; +use graphene_std::raster::Image; use graphite_editor::application::Editor; +use graphite_editor::consts::DEFAULT_DOCUMENT_NAME; use graphite_editor::messages::prelude::*; +use std::fs; use std::sync::Arc; use std::sync::mpsc::Sender; use std::thread; @@ -75,7 +79,7 @@ impl WinitApp { String::new() }); let message = PortfolioMessage::OpenDocumentFile { - document_name: path.file_name().and_then(|s| s.to_str()).unwrap_or("unknown").to_string(), + document_name: path.file_stem().and_then(|s| s.to_str()).unwrap_or("unknown").to_string(), document_serialized_content: content, }; let _ = event_loop_proxy.send_event(CustomEvent::DispatchMessage(message.into())); @@ -264,6 +268,75 @@ impl ApplicationHandler for WinitApp { let Some(event) = self.cef_context.handle_window_event(event) else { return }; match event { + // Currently not supported on wayland see https://github.com/rust-windowing/winit/issues/1881 + WindowEvent::DroppedFile(path) => { + let name = path.file_stem().and_then(|s| s.to_str()).map(|s| s.to_string()); + let Some(extension) = path.extension().and_then(|s| s.to_str()) else { + tracing::warn!("Unsupported file dropped: {}", path.display()); + // Fine to early return since we don't need to do cef work in this case + return; + }; + let load_string = |path: &std::path::PathBuf| { + let Ok(content) = fs::read_to_string(path) else { + tracing::error!("Failed to read file: {}", path.display()); + return None; + }; + + if content.is_empty() { + tracing::warn!("Dropped file is empty: {}", path.display()); + return None; + } + Some(content) + }; + // 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 + match extension { + "graphite" => { + let Some(content) = load_string(&path) else { return }; + + let message = PortfolioMessage::OpenDocumentFile { + document_name: name.unwrap_or(DEFAULT_DOCUMENT_NAME.to_string()), + document_serialized_content: content, + }; + self.dispatch_message(message.into()); + } + "svg" => { + let Some(content) = load_string(&path) else { return }; + + let message = PortfolioMessage::PasteSvg { + name: path.file_stem().map(|s| s.to_string_lossy().to_string()), + svg: content, + mouse: None, + parent_and_insert_index: None, + }; + self.dispatch_message(message.into()); + } + _ => match image::ImageReader::open(&path) { + Ok(reader) => match reader.decode() { + Ok(image) => { + let width = image.width(); + let height = image.height(); + // TODO: support loading images with more than 8 bits per channel + let image_data = image.to_rgba8(); + let image = Image::::from_image_data(image_data.as_raw(), width, height); + + let message = PortfolioMessage::PasteImage { + name, + image, + mouse: None, + parent_and_insert_index: None, + }; + self.dispatch_message(message.into()); + } + Err(e) => { + tracing::error!("Failed to decode image: {}: {}", path.display(), e); + } + }, + Err(e) => { + tracing::error!("Failed to open image file: {}: {}", path.display(), e); + } + }, + } + } WindowEvent::CloseRequested => { tracing::info!("The close button was pressed; stopping"); event_loop.exit(); diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 9ec886fc9c..ab1b86dad0 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -781,7 +781,7 @@ impl MessageHandler> for Portfolio if create_document { responses.add(PortfolioMessage::NewDocumentWithName { - name: name.clone().unwrap_or("Untitled Document".into()), + name: name.clone().unwrap_or(DEFAULT_DOCUMENT_NAME.into()), }); } @@ -812,7 +812,7 @@ impl MessageHandler> for Portfolio if create_document { responses.add(PortfolioMessage::NewDocumentWithName { - name: name.clone().unwrap_or("Untitled Document".into()), + name: name.clone().unwrap_or(DEFAULT_DOCUMENT_NAME.into()), }); }