Clipboard: Cross-application copy-paste support - #4499
Conversation
There was a problem hiding this comment.
1 issue found across 8 files
Confidence score: 3/5
- In
editor/src/node_graph_executor/runtime.rs, selections containing an artboard currently discard theList<Artboard>monitor output and emit an empty SVG, so rendered previews or exports can lose the artboard’s content, position, dimensions, and clipping; render artboard outputs as SVG while preserving those properties.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="editor/src/node_graph_executor/runtime.rs">
<violation number="1" location="editor/src/node_graph_executor/runtime.rs:351">
P1: When the selection contains an artboard, this branch ignores its `List<Artboard>` monitor output and sends an empty SVG. Render artboard outputs as SVG too, preserving their location, dimensions, and clipping.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| return texture; | ||
| } | ||
| GraphRuntimeRequest::CopySvgTextClipboard(text_string_clipboard, selected_node_ids) => { | ||
| let mut combined_graphics = List::<Graphic>::new(); |
There was a problem hiding this comment.
P1: When the selection contains an artboard, this branch ignores its List<Artboard> monitor output and sends an empty SVG. Render artboard outputs as SVG too, preserving their location, dimensions, and clipping.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/node_graph_executor/runtime.rs, line 351:
<comment>When the selection contains an artboard, this branch ignores its `List<Artboard>` monitor output and sends an empty SVG. Render artboard outputs as SVG too, preserving their location, dimensions, and clipping.</comment>
<file context>
@@ -340,6 +347,59 @@ impl NodeRuntime {
return texture;
}
+ GraphRuntimeRequest::CopySvgTextClipboard(text_string_clipboard, selected_node_ids) => {
+ let mut combined_graphics = List::<Graphic>::new();
+
+ for monitor_node_path in &self.monitor_nodes {
</file context>
There was a problem hiding this comment.
I don't think one can copy the artboard as is, even if they copy every content in the artboard, the copying type would still be graphic (of all selected nodes/layers) ig.
0HyperCube
left a comment
There was a problem hiding this comment.
Works well; thanks for your work so far.
|
I would like to wait with this until winit update is merged. And we might want to make this API lazy. |
There was a problem hiding this comment.
2 issues found across 11 files (changes from recent commits).
Confidence score: 3/5
- In
editor/src/node_graph_executor/runtime.rs, coalescing copy and evaluation can inspect monitor nodes before execution refreshes them, leaving the clipboard with stale or empty SVG; ensure the pending execution refreshes monitor state before clipboard introspection. - In
editor/src/node_graph_executor/runtime.rs, movingsvg_clipboardahead ofexecutioncan letCopySvgTextClipboardreturn early whencombined_graphicsis empty, skipping a pending render; preserve the required execution ordering or avoid short-circuiting the render.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="editor/src/node_graph_executor/runtime.rs">
<violation number="1" location="editor/src/node_graph_executor/runtime.rs:203">
P2: When a copy request and a graph evaluation are coalesced in one runtime pass, this line introspects monitor nodes before the pending execution refreshes them, so the clipboard can contain stale or empty SVG. Ensure the copy path reads output from the relevant completed execution, and avoid relying only on request reordering because the execution branch currently returns before later requests.</violation>
<violation number="2" location="editor/src/node_graph_executor/runtime.rs:203">
P2: Moving `svg_clipboard` ahead of `execution` lets a pending `CopySvgTextClipboard` request short-circuit a pending render. The `CopySvgTextClipboard` branch returns `None` early when `combined_graphics.is_empty()` (the case for `copy_svg_clipboard(graphite_json, Vec::new())` in portfolio_message_handler.rs:1697 and for an empty selection). Because the loop processes `svg_clipboard` first, that `return None` now skips the `ExecutionRequest` branch entirely, so the queued viewport render is dropped and its `ExecutionResponse` is never sent. Before this reorder the execution ran first and the copy was skipped instead. Consider processing the SVG copy without the early `return None` (e.g., `continue` after sending the clipboard) so a concurrent execution request still runs.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| } | ||
|
|
||
| let requests = [preferences, graph, eyedropper, execution].into_iter().flatten(); | ||
| let requests = [preferences, graph, eyedropper, svg_clipboard, execution].into_iter().flatten(); |
There was a problem hiding this comment.
P2: When a copy request and a graph evaluation are coalesced in one runtime pass, this line introspects monitor nodes before the pending execution refreshes them, so the clipboard can contain stale or empty SVG. Ensure the copy path reads output from the relevant completed execution, and avoid relying only on request reordering because the execution branch currently returns before later requests.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/node_graph_executor/runtime.rs, line 203:
<comment>When a copy request and a graph evaluation are coalesced in one runtime pass, this line introspects monitor nodes before the pending execution refreshes them, so the clipboard can contain stale or empty SVG. Ensure the copy path reads output from the relevant completed execution, and avoid relying only on request reordering because the execution branch currently returns before later requests.</comment>
<file context>
@@ -200,7 +200,7 @@ impl NodeRuntime {
}
- let requests = [preferences, graph, eyedropper, execution, svg_clipboard].into_iter().flatten();
+ let requests = [preferences, graph, eyedropper, svg_clipboard, execution].into_iter().flatten();
for request in requests {
</file context>
There was a problem hiding this comment.
This is technically true however it probably isn't very relevant since the user would be copying an SVG that is not yet rendered into the viewport.
You could fix this by having a result variable instead of an early return:
let mut result = None;
for request in requests {
match request {
// ...
GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, mut render_config, .. }) => {
// ...
result = Some(texture);
}
}
}
result
| } | ||
|
|
||
| let requests = [preferences, graph, eyedropper, execution].into_iter().flatten(); | ||
| let requests = [preferences, graph, eyedropper, svg_clipboard, execution].into_iter().flatten(); |
There was a problem hiding this comment.
P2: Moving svg_clipboard ahead of execution lets a pending CopySvgTextClipboard request short-circuit a pending render. The CopySvgTextClipboard branch returns None early when combined_graphics.is_empty() (the case for copy_svg_clipboard(graphite_json, Vec::new()) in portfolio_message_handler.rs:1697 and for an empty selection). Because the loop processes svg_clipboard first, that return None now skips the ExecutionRequest branch entirely, so the queued viewport render is dropped and its ExecutionResponse is never sent. Before this reorder the execution ran first and the copy was skipped instead. Consider processing the SVG copy without the early return None (e.g., continue after sending the clipboard) so a concurrent execution request still runs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/node_graph_executor/runtime.rs, line 203:
<comment>Moving `svg_clipboard` ahead of `execution` lets a pending `CopySvgTextClipboard` request short-circuit a pending render. The `CopySvgTextClipboard` branch returns `None` early when `combined_graphics.is_empty()` (the case for `copy_svg_clipboard(graphite_json, Vec::new())` in portfolio_message_handler.rs:1697 and for an empty selection). Because the loop processes `svg_clipboard` first, that `return None` now skips the `ExecutionRequest` branch entirely, so the queued viewport render is dropped and its `ExecutionResponse` is never sent. Before this reorder the execution ran first and the copy was skipped instead. Consider processing the SVG copy without the early `return None` (e.g., `continue` after sending the clipboard) so a concurrent execution request still runs.</comment>
<file context>
@@ -200,7 +200,7 @@ impl NodeRuntime {
}
- let requests = [preferences, graph, eyedropper, execution, svg_clipboard].into_iter().flatten();
+ let requests = [preferences, graph, eyedropper, svg_clipboard, execution].into_iter().flatten();
for request in requests {
</file context>
|
Hey @timon-schelling , we can wait for the API to be merged and then do the changes, what do you mean for the API to be lazy here? |
See the top level API definitions in rust-windowing/winit#4658 Both the DnD and Clipboard APIs are lazy in winit, meaning data only needs to be constructed once the other application accepts (based on data type). I think we should model it in a similar way.
We usually do that when something is merged. |
There was a problem hiding this comment.
1 issue found across 6 files (changes from recent commits).
Confidence score: 3/5
- In
editor/src/messages/clipboard/clipboard_message_handler.rs, Chrome/Edge text copying can fail becauseOption::Nonebecomes JSONnull, which passes the frontend’ssvg_string !== undefinedcheck and leads to invalid clipboard construction; align the null/undefined handling before merging.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="editor/src/messages/clipboard/clipboard_message_handler.rs">
<violation number="1" location="editor/src/messages/clipboard/clipboard_message_handler.rs:91">
P1: Copying a text selection breaks on Chrome/Edge: the frontend receives `svg_string` as `null` (serde serializes Rust `Option::None` as JSON null), so `data.svg_string !== undefined` is true, and it builds `new ClipboardItem({ "image/svg+xml": null, ... })`, which throws a TypeError and never writes the text. The guard must treat `null` as absent too (`data.svg_string != null`), or the text case must not be routed through `TriggerClipboardSvgAndJsonWrite`.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| responses.add(PortfolioMessage::RequestSvgTextCopy { graphite_json }); | ||
| } | ||
| ClipboardContent::Text(graphite_json) => { | ||
| responses.add(FrontendMessage::TriggerClipboardSvgAndJsonWrite { svg_string: None, graphite_json }); |
There was a problem hiding this comment.
P1: Copying a text selection breaks on Chrome/Edge: the frontend receives svg_string as null (serde serializes Rust Option::None as JSON null), so data.svg_string !== undefined is true, and it builds new ClipboardItem({ "image/svg+xml": null, ... }), which throws a TypeError and never writes the text. The guard must treat null as absent too (data.svg_string != null), or the text case must not be routed through TriggerClipboardSvgAndJsonWrite.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/clipboard/clipboard_message_handler.rs, line 91:
<comment>Copying a text selection breaks on Chrome/Edge: the frontend receives `svg_string` as `null` (serde serializes Rust `Option::None` as JSON null), so `data.svg_string !== undefined` is true, and it builds `new ClipboardItem({ "image/svg+xml": null, ... })`, which throws a TypeError and never writes the text. The guard must treat `null` as absent too (`data.svg_string != null`), or the text case must not be routed through `TriggerClipboardSvgAndJsonWrite`.</comment>
<file context>
@@ -87,8 +87,8 @@ impl MessageHandler<ClipboardMessage, ClipboardMessageContext<'_>> for Clipboard
- ClipboardContent::Text(text) => {
- responses.add(FrontendMessage::TriggerClipboardWrite { content: text });
+ ClipboardContent::Text(graphite_json) => {
+ responses.add(FrontendMessage::TriggerClipboardSvgAndJsonWrite { svg_string: None, graphite_json });
}
},
</file context>
There was a problem hiding this comment.
The None does become an undefined in js.
There was a problem hiding this comment.
1 existing issue remains and 3 new issues found across 8 files (changes from recent commits).
Confidence score: 2/5
editor/src/node_graph_executor/runtime.rsincollect_graphicsdrops rank-0Item<Graphic>monitor output, causingCopySvgTextClipboardto send an empty SVG for selected layers; preserve the rank-0 output alongside list graphics.frontend/src/managers/clipboard.tsbuilds aClipboardItemwith a null SVG for text-only copies because the new guard does not trigger, so clipboard writes throw instead of falling back towriteText; fix the SVG presence check and fallback path.editor/src/messages/clipboard/clipboard_message_handler.rslabels plain caret text fromReadSelectionas if it were serialized Graphite content, which can mislead downstream clipboard handling; keep the binding aligned with the actual payload.node-graph/libraries/core-types/src/transform.rsapplies absolute resolution to reversed bounds while translating frombounds[0], potentially placing transformed content at the wrong corner; make translation consistent with reversed-bound handling.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="editor/src/messages/clipboard/clipboard_message_handler.rs">
<violation number="1" location="editor/src/messages/clipboard/clipboard_message_handler.rs:90">
P3: `ClipboardContent::Text` is only ever constructed from `ReadSelection { content }`, which carries the plain text read at the caret (`readAtCaret`), not a serialized Graphite payload. Renaming the binding to `graphite_json` and passing it through the `graphite_json` field of `TriggerClipboardSvgAndJsonWrite` is misleading: the value written to the system clipboard here is plain text (the frontend's else branch does `writeText` when `svg_string` is None). A future maintainer will reasonably assume this field holds a `graphite:`-prefixed JSON payload. Use a name that reflects the value, e.g. `text`, and note clearly that plain text copy intentionally sends `svg_string: None` with the text in the graphite_json slot.</violation>
</file>
<file name="node-graph/libraries/core-types/src/transform.rs">
<violation number="1" location="node-graph/libraries/core-types/src/transform.rs:192">
P3: The resolution clamps with `.abs()` while the translation always uses `bounds[0]`, so reversed bounds would render at the wrong corner. The function's `.abs()` implies it handles reversed bounds but the translation does not. Since callers pass normalized `[min, max]` bounds, either drop the `.abs()` (documenting the `[min, max]` contract) or normalize both corners consistently.</violation>
</file>
<file name="editor/src/node_graph_executor/runtime.rs">
<violation number="1" location="editor/src/node_graph_executor/runtime.rs:568">
P1: When a selected layer's monitor output is `Item<Graphic>`, `collect_graphics` drops it and `CopySvgTextClipboard` sends an empty SVG. Preserve the rank-0 branch by pushing `io.output` alongside the existing `List<Graphic>` handling.</violation>
</file>
Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| if let Ok(introspected_data) = self.executor.introspect(monitor_node_path) | ||
| && let Some(io) = introspected_data.downcast_ref::<IORecord<Context, List<Graphic>>>() | ||
| { | ||
| combined_graphics.extend(io.output.clone()); | ||
| } else { | ||
| warn!("No graphic type is matched while extracting svg"); | ||
| } |
There was a problem hiding this comment.
P1: When a selected layer's monitor output is Item<Graphic>, collect_graphics drops it and CopySvgTextClipboard sends an empty SVG. Preserve the rank-0 branch by pushing io.output alongside the existing List<Graphic> handling.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/node_graph_executor/runtime.rs, line 568:
<comment>When a selected layer's monitor output is `Item<Graphic>`, `collect_graphics` drops it and `CopySvgTextClipboard` sends an empty SVG. Preserve the rank-0 branch by pushing `io.output` alongside the existing `List<Graphic>` handling.</comment>
<file context>
@@ -589,6 +550,36 @@ impl NodeRuntime {
+
+ if selected_node_ids.contains(&parent_network_node_id) {
+ // Introspect using the full monitor node path
+ if let Ok(introspected_data) = self.executor.introspect(monitor_node_path)
+ && let Some(io) = introspected_data.downcast_ref::<IORecord<Context, List<Graphic>>>()
+ {
</file context>
| if let Ok(introspected_data) = self.executor.introspect(monitor_node_path) | |
| && let Some(io) = introspected_data.downcast_ref::<IORecord<Context, List<Graphic>>>() | |
| { | |
| combined_graphics.extend(io.output.clone()); | |
| } else { | |
| warn!("No graphic type is matched while extracting svg"); | |
| } | |
| if let Ok(introspected_data) = self.executor.introspect(monitor_node_path) { | |
| if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, List<Graphic>>>() { | |
| combined_graphics.extend(io.output.clone()); | |
| } else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Item<Graphic>>>() { | |
| combined_graphics.push(io.output.clone()); | |
| } else { | |
| warn!("No graphic type is matched while extracting svg"); | |
| } | |
| } else { | |
| warn!("No graphic type is matched while extracting svg"); | |
| } |
There was a problem hiding this comment.
This doesn't happen as it will always be made into a group.
| ClipboardContent::Text(graphite_json) => { | ||
| responses.add(FrontendMessage::TriggerClipboardSvgAndJsonWrite { svg_string: None, graphite_json }); | ||
| } |
There was a problem hiding this comment.
P3: ClipboardContent::Text is only ever constructed from ReadSelection { content }, which carries the plain text read at the caret (readAtCaret), not a serialized Graphite payload. Renaming the binding to graphite_json and passing it through the graphite_json field of TriggerClipboardSvgAndJsonWrite is misleading: the value written to the system clipboard here is plain text (the frontend's else branch does writeText when svg_string is None). A future maintainer will reasonably assume this field holds a graphite:-prefixed JSON payload. Use a name that reflects the value, e.g. text, and note clearly that plain text copy intentionally sends svg_string: None with the text in the graphite_json slot.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/clipboard/clipboard_message_handler.rs, line 90:
<comment>`ClipboardContent::Text` is only ever constructed from `ReadSelection { content }`, which carries the plain text read at the caret (`readAtCaret`), not a serialized Graphite payload. Renaming the binding to `graphite_json` and passing it through the `graphite_json` field of `TriggerClipboardSvgAndJsonWrite` is misleading: the value written to the system clipboard here is plain text (the frontend's else branch does `writeText` when `svg_string` is None). A future maintainer will reasonably assume this field holds a `graphite:`-prefixed JSON payload. Use a name that reflects the value, e.g. `text`, and note clearly that plain text copy intentionally sends `svg_string: None` with the text in the graphite_json slot.</comment>
<file context>
@@ -87,8 +87,8 @@ impl MessageHandler<ClipboardMessage, ClipboardMessageContext<'_>> for Clipboard
}
- ClipboardContent::Text(text) => {
- responses.add(FrontendMessage::TriggerClipboardWrite { content: text });
+ ClipboardContent::Text(graphite_json) => {
+ responses.add(FrontendMessage::TriggerClipboardSvgAndJsonWrite { svg_string: None, graphite_json });
}
</file context>
| ClipboardContent::Text(graphite_json) => { | |
| responses.add(FrontendMessage::TriggerClipboardSvgAndJsonWrite { svg_string: None, graphite_json }); | |
| } | |
| ClipboardContent::Text(text) => { | |
| responses.add(FrontendMessage::TriggerClipboardSvgAndJsonWrite { svg_string: None, graphite_json: text }); | |
| } |
| pub fn from_bounds(bounds: [DVec2; 2], quality: RenderQuality) -> Self { | ||
| Footprint { | ||
| transform: DAffine2::from_translation(DVec2::new(bounds[0].x, bounds[0].y)), | ||
| resolution: UVec2::new((bounds[1].x - bounds[0].x).abs().ceil() as u32, (bounds[1].y - bounds[0].y).abs().ceil() as u32).max(UVec2::ONE), | ||
| quality, | ||
| } |
There was a problem hiding this comment.
P3: The resolution clamps with .abs() while the translation always uses bounds[0], so reversed bounds would render at the wrong corner. The function's .abs() implies it handles reversed bounds but the translation does not. Since callers pass normalized [min, max] bounds, either drop the .abs() (documenting the [min, max] contract) or normalize both corners consistently.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At node-graph/libraries/core-types/src/transform.rs, line 192:
<comment>The resolution clamps with `.abs()` while the translation always uses `bounds[0]`, so reversed bounds would render at the wrong corner. The function's `.abs()` implies it handles reversed bounds but the translation does not. Since callers pass normalized `[min, max]` bounds, either drop the `.abs()` (documenting the `[min, max]` contract) or normalize both corners consistently.</comment>
<file context>
@@ -189,6 +189,14 @@ impl Footprint {
quality: RenderQuality::Full,
};
+ pub fn from_bounds(bounds: [DVec2; 2], quality: RenderQuality) -> Self {
+ Footprint {
+ transform: DAffine2::from_translation(DVec2::new(bounds[0].x, bounds[0].y)),
</file context>
| pub fn from_bounds(bounds: [DVec2; 2], quality: RenderQuality) -> Self { | |
| Footprint { | |
| transform: DAffine2::from_translation(DVec2::new(bounds[0].x, bounds[0].y)), | |
| resolution: UVec2::new((bounds[1].x - bounds[0].x).abs().ceil() as u32, (bounds[1].y - bounds[0].y).abs().ceil() as u32).max(UVec2::ONE), | |
| quality, | |
| } | |
| pub fn from_bounds(bounds: [DVec2; 2], quality: RenderQuality) -> Self { | |
| Footprint { | |
| transform: DAffine2::from_translation(bounds[0]), | |
| resolution: UVec2::new((bounds[1].x - bounds[0].x).ceil() as u32, (bounds[1].y - bounds[0].y).ceil() as u32).max(UVec2::ONE), | |
| quality, | |
| } | |
| } |
| pub fn from_bounds(bounds: [DVec2; 2], quality: RenderQuality) -> Self { | ||
| Footprint { | ||
| transform: DAffine2::from_translation(DVec2::new(bounds[0].x, bounds[0].y)), | ||
| resolution: UVec2::new((bounds[1].x - bounds[0].x).abs().ceil() as u32, (bounds[1].y - bounds[0].y).abs().ceil() as u32).max(UVec2::ONE), |
There was a problem hiding this comment.
| resolution: UVec2::new((bounds[1].x - bounds[0].x).abs().ceil() as u32, (bounds[1].y - bounds[0].y).abs().ceil() as u32).max(UVec2::ONE), | |
| transform: DAffine2::from_translation(bounds[0].min(bounds[1])), | |
| resolution: (bounds[1] - bounds[0]).abs().ceil().as_uvec2().max(UVec2::ONE), |
| if let Ok(introspected_data) = self.executor.introspect(monitor_node_path) | ||
| && let Some(io) = introspected_data.downcast_ref::<IORecord<Context, List<Graphic>>>() | ||
| { | ||
| combined_graphics.extend(io.output.clone()); | ||
| } else { | ||
| warn!("No graphic type is matched while extracting svg"); | ||
| } |
There was a problem hiding this comment.
This doesn't happen as it will always be made into a group.
| responses.add(PortfolioMessage::RequestSvgTextCopy { graphite_json }); | ||
| } | ||
| ClipboardContent::Text(graphite_json) => { | ||
| responses.add(FrontendMessage::TriggerClipboardSvgAndJsonWrite { svg_string: None, graphite_json }); |
There was a problem hiding this comment.
The None does become an undefined in js.
|
|
||
| if combined_graphics.is_empty() { | ||
| self.sender.send_svg_text_clipboard(String::new(), text_string_clipboard); | ||
| return None; |
| let requests = [preferences, graph, eyedropper, svg_clipboard, execution].into_iter().flatten(); | ||
|
|
||
| for request in requests { | ||
| match request { |
There was a problem hiding this comment.
I think the control flow would be much easier if these were made into functions:
match request {
GraphRuntimeRequest::EditorPreferencesUpdate(preferences) => self.editor_preferences_update(preferences),
GraphRuntimeRequest::GraphUpdate(graph_update) => self.graph_update(graph_update),
...
}| } | ||
|
|
||
| let requests = [preferences, graph, eyedropper, execution].into_iter().flatten(); | ||
| let requests = [preferences, graph, eyedropper, svg_clipboard, execution].into_iter().flatten(); |
There was a problem hiding this comment.
This is technically true however it probably isn't very relevant since the user would be copying an SVG that is not yet rendered into the viewport.
You could fix this by having a result variable instead of an early return:
let mut result = None;
for request in requests {
match request {
// ...
GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, mut render_config, .. }) => {
// ...
result = Some(texture);
}
}
}
result
Description
This PR aims to resolve #2373. Solution's approach has been thoroughly discussed in discord's development channel. The PR will fix this issue by introducing copy of both text and svg+xml mime types when copying a selection to the clipboard. Essentially making it possible for other apps to take the svg representation while graphite picks up the internal json representation for copy pasting.
Notes
image/svg+xmlis not supported by firefox and safari Stable so that is handled.Following example showcase the same copy paste not working across apps (
kritaandinkscapeshown as the other apps) in current build vs it working in this branch while still being compatible across graphite tabs.Before(Only across graphite tabs, not across apps):
editor_before.mp4
After(Across tabs and other apps):
editor_after.mp4