Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 208 additions & 35 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ serde = { workspace = true }
clap = { workspace = true, features = ["derive"] }
pidlock = "0.2.2"
ctrlc = "3.5.1"
window_clipboard = "0.5"

# Windows-specific dependencies
[target.'cfg(target_os = "windows")'.dependencies]
Expand All @@ -64,4 +65,3 @@ objc2 = { version = "0.6.1", default-features = false }
objc2-foundation = { version = "0.3.2", default-features = false }
objc2-app-kit = { version = "0.3.2", default-features = false }
muda = { git = "https://github.com/tauri-apps/muda.git", rev = "3f460b8fbaed59cda6d95ceea6904f000f093f15", default-features = false }

12 changes: 12 additions & 0 deletions desktop/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,18 @@ impl App {
window.update_menu(entries);
}
}
DesktopFrontendMessage::ClipboardRead => {
if let Some(window) = &self.window {
let content = window.clipboard_read();
let message = DesktopWrapperMessage::ClipboardReadResult { content };
self.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));
}
}
DesktopFrontendMessage::ClipboardWrite { content } => {
if let Some(window) = &mut self.window {
window.clipboard_write(content);
}
}
DesktopFrontendMessage::WindowClose => {
self.app_event_scheduler.schedule(AppEvent::CloseWindow);
}
Expand Down
4 changes: 4 additions & 0 deletions desktop/src/cef/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat

key_event.character = event.logical_key.to_char_representation() as u16;

if event.state == ElementState::Pressed && key_event.character != 0 {
key_event.type_ = cef_key_event_type_t::KEYEVENT_CHAR.into();
}

// Mitigation for CEF on Mac bug to prevent NSMenu being triggered by this key event.
//
// CEF converts the key event into an `NSEvent` internally and passes that to Chromium.
Expand Down
6 changes: 5 additions & 1 deletion desktop/src/render/composite_shader.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
}

let overlay_srgb = textureSample(t_overlays, s_diffuse, viewport_coordinate);
let viewport_srgb = textureSample(t_viewport, s_diffuse, viewport_coordinate);
var viewport_srgb = textureSample(t_viewport, s_diffuse, viewport_coordinate);

if (viewport_srgb.a < 0.001) {
viewport_srgb = constants.background_color;
}

if (overlay_srgb.a < 0.001) {
if (ui_srgb.a < 0.001) {
Expand Down
19 changes: 19 additions & 0 deletions desktop/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub(crate) struct Window {
#[allow(dead_code)]
native_handle: native::NativeWindowImpl,
custom_cursors: HashMap<CustomCursorSource, CustomCursor>,
clipboard: window_clipboard::Clipboard,
}

impl Window {
Expand All @@ -57,10 +58,12 @@ impl Window {

let winit_window = event_loop.create_window(attributes).unwrap();
let native_handle = native::NativeWindowImpl::new(winit_window.as_ref(), app_event_scheduler);
let clipboard = unsafe { window_clipboard::Clipboard::connect(&winit_window) }.expect("failed to create clipboard");
Self {
winit_window: winit_window.into(),
native_handle,
custom_cursors: HashMap::new(),
clipboard,
}
}

Expand Down Expand Up @@ -136,6 +139,22 @@ impl Window {
pub(crate) fn update_menu(&self, entries: Vec<MenuItem>) {
self.native_handle.update_menu(entries);
}

pub(crate) fn clipboard_read(&self) -> Option<String> {
match self.clipboard.read() {
Ok(data) => Some(data),
Err(e) => {
tracing::error!("Failed to read from clipboard: {e}");
None
}
}
}

pub(crate) fn clipboard_write(&mut self, data: String) {
if let Err(e) = self.clipboard.write(data) {
tracing::error!("Failed to write to clipboard: {e}")
}
}
}

pub(crate) enum Cursor {
Expand Down
9 changes: 9 additions & 0 deletions desktop/wrapper/src/handle_desktop_wrapper_message.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use graphene_std::Color;
use graphene_std::raster::Image;
use graphite_editor::messages::app_window::app_window_message_handler::AppWindowPlatform;
use graphite_editor::messages::clipboard::utility_types::ClipboardContentRaw;
use graphite_editor::messages::prelude::*;

use super::DesktopWrapperMessageDispatcher;
Expand Down Expand Up @@ -156,5 +157,13 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess
}
#[cfg(not(target_os = "macos"))]
DesktopWrapperMessage::MenuEvent { id: _ } => {}
DesktopWrapperMessage::ClipboardReadResult { content } => {
if let Some(content) = content {
let message = ClipboardMessage::ReadClipboard {
content: ClipboardContentRaw::Text(content),
};
dispatcher.queue_editor_message(message);
}
}
}
}
6 changes: 6 additions & 0 deletions desktop/wrapper/src/intercept_frontend_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
_ => {}
}
}
FrontendMessage::TriggerClipboardRead => {
dispatcher.respond(DesktopFrontendMessage::ClipboardRead);
}
FrontendMessage::TriggerClipboardWrite { content } => {
dispatcher.respond(DesktopFrontendMessage::ClipboardWrite { content });
}
FrontendMessage::WindowClose => {
dispatcher.respond(DesktopFrontendMessage::WindowClose);
}
Expand Down
7 changes: 7 additions & 0 deletions desktop/wrapper/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ pub enum DesktopFrontendMessage {
UpdateMenu {
entries: Vec<MenuItem>,
},
ClipboardRead,
ClipboardWrite {
content: String,
},
WindowClose,
WindowMinimize,
WindowMaximize,
Expand Down Expand Up @@ -114,6 +118,9 @@ pub enum DesktopWrapperMessage {
MenuEvent {
id: String,
},
ClipboardReadResult {
content: Option<String>,
},
}

#[derive(Clone, serde::Serialize, serde::Deserialize, Debug)]
Expand Down
3 changes: 3 additions & 0 deletions editor/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct DispatcherMessageHandlers {
animation_message_handler: AnimationMessageHandler,
app_window_message_handler: AppWindowMessageHandler,
broadcast_message_handler: BroadcastMessageHandler,
clipboard_message_handler: ClipboardMessageHandler,
debug_message_handler: DebugMessageHandler,
defer_message_handler: DeferMessageHandler,
dialog_message_handler: DialogMessageHandler,
Expand Down Expand Up @@ -158,6 +159,7 @@ impl Dispatcher {
self.message_handlers.app_window_message_handler.process_message(message, &mut queue, ());
}
Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
Message::Clipboard(message) => self.message_handlers.clipboard_message_handler.process_message(message, &mut queue, ()),
Message::Debug(message) => {
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
}
Expand Down Expand Up @@ -319,6 +321,7 @@ impl Dispatcher {
// TODO: Reduce the number of heap allocations
let mut list = Vec::new();
list.extend(self.message_handlers.app_window_message_handler.actions());
list.extend(self.message_handlers.clipboard_message_handler.actions());
list.extend(self.message_handlers.dialog_message_handler.actions());
list.extend(self.message_handlers.animation_message_handler.actions());
list.extend(self.message_handlers.input_preprocessor_message_handler.actions());
Expand Down
13 changes: 13 additions & 0 deletions editor/src/messages/clipboard/clipboard_message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use crate::messages::clipboard::utility_types::{ClipboardContent, ClipboardContentRaw};
use crate::messages::prelude::*;

#[impl_message(Message, Clipboard)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum ClipboardMessage {
Cut,
Copy,
Paste,
ReadClipboard { content: ClipboardContentRaw },
ReadSelection { content: Option<String>, cut: bool },
Write { content: ClipboardContent },
}
85 changes: 85 additions & 0 deletions editor/src/messages/clipboard/clipboard_message_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use crate::messages::clipboard::utility_types::{ClipboardContent, ClipboardContentRaw};
use crate::messages::prelude::*;
use graphene_std::raster::Image;
use graphite_proc_macros::{ExtractField, message_handler_data};

const CLIPBOARD_PREFIX_LAYER: &str = "graphite/layer: ";
const CLIPBOARD_PREFIX_NODES: &str = "graphite/nodes: ";
const CLIPBOARD_PREFIX_VECTOR: &str = "graphite/vector: ";

#[derive(Debug, Clone, Default, ExtractField)]
pub struct ClipboardMessageHandler {}

#[message_handler_data]
impl MessageHandler<ClipboardMessage, ()> for ClipboardMessageHandler {
fn process_message(&mut self, message: ClipboardMessage, responses: &mut std::collections::VecDeque<Message>, _: ()) {
match message {
ClipboardMessage::Cut => responses.add(FrontendMessage::TriggerSelectionRead { cut: true }),
ClipboardMessage::Copy => responses.add(FrontendMessage::TriggerSelectionRead { cut: false }),
ClipboardMessage::Paste => responses.add(FrontendMessage::TriggerClipboardRead),
ClipboardMessage::ReadClipboard { content } => match content {
ClipboardContentRaw::Text(text) => {
if let Some(layer) = text.strip_prefix(CLIPBOARD_PREFIX_LAYER) {
responses.add(PortfolioMessage::PasteSerializedData { data: layer.to_string() });
} else if let Some(nodes) = text.strip_prefix(CLIPBOARD_PREFIX_NODES) {
responses.add(NodeGraphMessage::PasteNodes { serialized_nodes: nodes.to_string() });
} else if let Some(vector) = text.strip_prefix(CLIPBOARD_PREFIX_VECTOR) {
responses.add(PortfolioMessage::PasteSerializedVector { data: vector.to_string() });
} else {
responses.add(FrontendMessage::TriggerSelectionWrite { content: text });
}
}
ClipboardContentRaw::Svg(svg) => {
responses.add(PortfolioMessage::PasteSvg {
svg,
name: None,
mouse: None,
parent_and_insert_index: None,
});
}
ClipboardContentRaw::Image { data, width, height } => {
responses.add(PortfolioMessage::PasteImage {
image: Image::from_image_data(&data, width, height),
name: None,
mouse: None,
parent_and_insert_index: None,
});
}
},
ClipboardMessage::ReadSelection { content, cut } => {
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
if let Some(text) = content {
responses.add(ClipboardMessage::Write {
content: ClipboardContent::Text(text),
});
} else if cut {
responses.add(PortfolioMessage::Cut { clipboard: Clipboard::Device });
} else {
responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Device });
}
}
ClipboardMessage::Write { content } => {
let text = match content {
ClipboardContent::Svg(_) => {
log::error!("SVG copying is not yet supported");
return;
}
ClipboardContent::Image { .. } => {
log::error!("Image copying is not yet supported");
return;
}
ClipboardContent::Layer(layer) => format!("{CLIPBOARD_PREFIX_LAYER}{layer}"),
ClipboardContent::Nodes(nodes) => format!("{CLIPBOARD_PREFIX_NODES}{nodes}"),
ClipboardContent::Vector(vector) => format!("{CLIPBOARD_PREFIX_VECTOR}{vector}"),
ClipboardContent::Text(text) => text,
};
responses.add(FrontendMessage::TriggerClipboardWrite { content: text });
}
}
}
advertise_actions!(ClipboardMessageDiscriminant;
Cut,
Copy,
Paste,
);
}
8 changes: 8 additions & 0 deletions editor/src/messages/clipboard/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
mod clipboard_message;
pub mod clipboard_message_handler;
pub mod utility_types;

#[doc(inline)]
pub use clipboard_message::{ClipboardMessage, ClipboardMessageDiscriminant};
#[doc(inline)]
pub use clipboard_message_handler::ClipboardMessageHandler;
16 changes: 16 additions & 0 deletions editor/src/messages/clipboard/utility_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum ClipboardContentRaw {
Text(String),
Svg(String),
Image { data: Vec<u8>, width: u32, height: u32 },
}

#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum ClipboardContent {
Layer(String),
Nodes(String),
Vector(String),
Text(String),
Svg(String),
Image { data: Vec<u8>, width: u32, height: u32 },
}
20 changes: 14 additions & 6 deletions editor/src/messages/frontend/frontend_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ pub enum FrontendMessage {
shortcut: Option<ActionShortcut>,
},

// Trigger prefix: cause a browser API to do something
// Trigger prefix: cause a frontend specific API to do something
TriggerAboutGraphiteLocalizedCommitDate {
#[serde(rename = "commitDate")]
commit_date: String,
Expand Down Expand Up @@ -111,7 +111,6 @@ pub enum FrontendMessage {
TriggerOpenLaunchDocuments,
TriggerLoadPreferences,
TriggerOpenDocument,
TriggerPaste,
TriggerSavePreferences {
preferences: PreferencesMessageHandler,
},
Expand All @@ -120,13 +119,19 @@ pub enum FrontendMessage {
document_id: DocumentId,
},
TriggerTextCommit,
TriggerTextCopy {
#[serde(rename = "copyText")]
copy_text: String,
},
TriggerVisitLink {
url: String,
},
TriggerClipboardRead,
TriggerClipboardWrite {
content: String,
},
TriggerSelectionRead {
cut: bool,
},
TriggerSelectionWrite {
content: String,
},

// Update prefix: give the frontend a new value or state for it to use
UpdateActiveDocument {
Expand Down Expand Up @@ -330,12 +335,15 @@ pub enum FrontendMessage {
width: f64,
height: f64,
},

#[cfg(not(target_family = "wasm"))]
RenderOverlays {
#[serde(skip, default = "OverlayContext::default")]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
context: OverlayContext,
},

// Window prefix: cause the application window to do something
WindowClose,
WindowMinimize,
WindowMaximize,
Expand Down
8 changes: 5 additions & 3 deletions editor/src/messages/input_mapper/input_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ pub fn input_mappings() -> Mapping {
// Hack to prevent Left Click + Accel + Z combo (this effectively blocks you from making a double undo with AbortTransaction)
entry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop),
//
// ClipboardMessage
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=ClipboardMessage::Cut),
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=ClipboardMessage::Copy),
entry!(KeyDown(KeyV); modifiers=[Accel], action_dispatch=ClipboardMessage::Paste),
//
// NodeGraphMessage
entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: false }),
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: false, alt_click: false, right_click: false }),
Expand Down Expand Up @@ -433,9 +438,6 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(KeyR); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleRulers),
entry!(KeyDown(KeyD); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleDataPanelOpen),
//
// FrontendMessage
entry!(KeyDown(KeyV); modifiers=[Accel], action_dispatch=FrontendMessage::TriggerPaste),
//
// DialogMessage
entry!(KeyDown(KeyE); modifiers=[Accel], action_dispatch=DialogMessage::RequestExportDialog),
entry!(KeyDown(KeyN); modifiers=[Accel], action_dispatch=DialogMessage::RequestNewDocumentDialog),
Expand Down
Loading