Bundle Graphite using Tauri (#873)

* Setup tauri component for graphite editor

Integrate graphite into tauri app

Split interpreted-executor out of graph-craft

* Add gpu execution node

* General Cleanup
This commit is contained in:
TrueDoctor 2022-12-07 12:49:34 +01:00 committed by Keavon Chambers
parent 52cc770a1e
commit 7d8f94462a
109 changed files with 5661 additions and 544 deletions

View file

@ -27,6 +27,12 @@ jobs:
cd frontend
npm ci
- name: 🔧 Install libgtk
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: libgtk-3-dev libssl-dev build-essential curl wget libayatana-appindicator3-dev librsvg2-dev libsoup2.4-dev libjavascriptcoregtk-4.0-dev libwebkit2gtk-4.0-dev
version: 1.0
- name: 🔼 Update Rust to latest stable
run: |
rustc --version

3145
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,9 +4,11 @@ members = [
"graphene",
"proc-macros",
"frontend/wasm",
"frontend/src-tauri",
"node-graph/gcore",
"node-graph/gstd",
"node-graph/graph-craft",
"node-graph/interpreted-executor",
"node-graph/borrow_stack",
"libraries/dyn-any",
"libraries/bezier-rs",

View file

@ -1,11 +1,14 @@
accepted = [
"Apache-2.0",
"MIT",
"MIT-0",
"BSD-2-Clause",
"BSD-3-Clause",
"BSD-2-Clause",
"Zlib",
"Unicode-DFS-2016",
"ISC",
"MPL-2.0",
]
ignore-build-dependencies = true
ignore-dev-dependencies = true

View file

@ -49,6 +49,7 @@ notice = "warn"
# output a note when they are encountered.
ignore = [
#"RUSTSEC-0000-0000",
"RUSTSEC-2020-0071", # This has been fixed in the version of chrono we use
]
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
# lower than the range specified will be ignored. Note that ignored advisories
@ -71,13 +72,14 @@ unlicensed = "deny"
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
allow = [
"MIT",
"MIT-0",
"Apache-2.0",
"BSD-3-Clause",
"BSD-2-Clause",
"Zlib",
"Zlib",
"Unicode-DFS-2016",
"ISC",
"MPL-2.0",
#"Apache-2.0 WITH LLVM-exception",
]
# List of explicitly disallowed licenses

View file

@ -10,6 +10,9 @@ homepage = "https://graphite.rs"
repository = "https://github.com/GraphiteEditor/Graphite"
license = "Apache-2.0"
[features]
gpu = ["graph-craft/gpu", "interpreted-executor/gpu"]
[dependencies]
log = "0.4"
bitflags = "1.2.1"
@ -18,7 +21,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
graphite-proc-macros = { path = "../proc-macros" }
bezier-rs = { path = "../libraries/bezier-rs" }
glam = { version="0.17", features = ["serde"] }
glam = { version="0.22", features = ["serde"] }
rand_chacha = "0.3.1"
spin = "0.9.2"
kurbo = { git = "https://github.com/linebender/kurbo.git", features = [
@ -31,6 +34,7 @@ once_cell = "1.13.0" # Remove when `core::cell::OnceCell` is stabilized (<https:
# Node graph
image = { version = "0.24", default-features = false, features = ["bmp"] }
graph-craft = { path = "../node-graph/graph-craft" }
interpreted-executor = { path = "../node-graph/interpreted-executor" }
borrow_stack = { path = "../node-graph/borrow_stack" }
dyn-any = { path = "../libraries/dyn-any" }
graphene-core = { path = "../node-graph/gcore" }

View file

@ -3,7 +3,7 @@ use crate::messages::layout::utility_types::layout_widget::SubLayout;
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::menu_widgets::MenuBarEntry;
use crate::messages::portfolio::document::node_graph::{FrontendNode, FrontendNodeLink, FrontendNodeType};
use crate::messages::portfolio::document::utility_types::layer_panel::{LayerPanelEntry, RawBuffer};
use crate::messages::portfolio::document::utility_types::layer_panel::{JsRawBuffer, LayerPanelEntry, RawBuffer};
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::HintData;
@ -156,6 +156,10 @@ pub enum FrontendMessage {
#[serde(rename = "dataBuffer")]
data_buffer: RawBuffer,
},
UpdateDocumentLayerTreeStructureJs {
#[serde(rename = "dataBuffer")]
data_buffer: JsRawBuffer,
},
UpdateDocumentModeLayout {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,

View file

@ -16,7 +16,7 @@ pub struct FrontendImageData {
pub path: Vec<LayerId>,
pub mime: String,
#[serde(skip)]
pub image_data: std::rc::Rc<Vec<u8>>,
pub image_data: std::sync::Arc<Vec<u8>>,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]

View file

@ -4,7 +4,6 @@ use crate::messages::prelude::*;
pub use graphene::DocumentResponse;
use bitflags::bitflags;
use serde::ser::SerializeStruct;
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
@ -51,7 +50,7 @@ bitflags! {
// (although we ignore the shift key, so the user doesn't have to press `Ctrl Shift +` on a US keyboard), even if the keyboard layout
// is for a different locale where the `+` key is somewhere entirely different, shifted or not. This would then also work for numpad `+`.
#[impl_message(Message, InputMapperMessage, KeyDown)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub enum Key {
// Writing system keys
Digit0,
@ -210,18 +209,6 @@ pub enum Key {
NumKeys,
}
impl Serialize for Key {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let key = format!("{:?}", self);
let label = self.to_string();
let mut state = serializer.serialize_struct("KeyWithLabel", 2)?;
state.serialize_field("key", &key)?;
state.serialize_field("label", &label)?;
state.end()
}
}
impl fmt::Display for Key {
// TODO: Relevant key labels should be localized when we get around to implementing localization/internationalization
fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
@ -308,6 +295,35 @@ impl fmt::Display for Key {
}
}
impl From<Key> for LayoutKey {
fn from(key: Key) -> Self {
Self {
key: format!("{:?}", key),
label: key.to_string(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct LayoutKey {
key: String,
label: String,
}
/*
impl Serialize for Key {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let key = format!("{:?}", self.0);
let label = self.0.to_string();
assert_eq!(serde_json::to_string(Key::KeyEscape), {"key": KeyEscape, "label": "Esc"});
let mut state = serializer.serialize_struct("KeyWithLabel", 2)?;
state.serialize_field("key", &key)?;
state.serialize_field("label", &label)?;
state.end()
}
}*/
pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize;
/// Only `Key`s that exist on a physical keyboard should be used.
@ -342,6 +358,22 @@ impl fmt::Display for KeysGroup {
}
}
impl From<KeysGroup> for String {
fn from(keys: KeysGroup) -> Self {
let layout_keys: LayoutKeysGroup = keys.into();
serde_json::to_string(&layout_keys).expect("Failed to serialize KeysGroup")
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct LayoutKeysGroup(Vec<LayoutKey>);
impl From<KeysGroup> for LayoutKeysGroup {
fn from(keys_group: KeysGroup) -> Self {
Self(keys_group.0.into_iter().map(|key| key.into()).collect())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum MouseMotion {
None,

View file

@ -1,4 +1,4 @@
use super::input_keyboard::{all_required_modifiers_pressed, KeysGroup};
use super::input_keyboard::{all_required_modifiers_pressed, KeysGroup, LayoutKeysGroup};
use crate::messages::input_mapper::default_mapping::default_mapping;
use crate::messages::input_mapper::utility_types::input_keyboard::{KeyStates, NUMBER_OF_KEYS};
use crate::messages::prelude::*;
@ -81,24 +81,27 @@ pub struct MappingEntry {
pub enum ActionKeys {
Action(MessageDiscriminant),
#[serde(rename = "keys")]
Keys(KeysGroup),
Keys(LayoutKeysGroup),
}
impl ActionKeys {
pub fn to_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) {
pub fn to_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) -> String {
match self {
ActionKeys::Action(action) => {
if let Some(keys) = action_input_mapping(action).get_mut(0) {
let mut taken_keys = KeysGroup::default();
std::mem::swap(keys, &mut taken_keys);
*self = ActionKeys::Keys(taken_keys);
let description = taken_keys.to_string();
*self = ActionKeys::Keys(taken_keys.into());
description
} else {
*self = ActionKeys::Keys(KeysGroup::default());
*self = ActionKeys::Keys(KeysGroup::default().into());
String::new()
}
}
ActionKeys::Keys(keys) => {
warn!("Calling `.to_keys()` on a `ActionKeys::Keys` is a mistake/bug. Keys are: {:?}.", keys);
String::new()
}
}
}

View file

@ -10,7 +10,7 @@ use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
use std::rc::Rc;
use std::sync::Arc;
pub trait PropertyHolder {
fn properties(&self) -> Layout {
@ -39,11 +39,9 @@ impl Layout {
if let Layout::WidgetLayout(mut widget_layout) = self {
// Function used multiple times later in this code block to convert `ActionKeys::Action` to `ActionKeys::Keys` and append its shortcut to the tooltip
let apply_shortcut_to_tooltip = |tooltip_shortcut: &mut ActionKeys, tooltip: &mut String| {
tooltip_shortcut.to_keys(action_input_mapping);
if let ActionKeys::Keys(keys) = tooltip_shortcut {
let shortcut_text = keys.to_string();
let shortcut_text = tooltip_shortcut.to_keys(action_input_mapping);
if let ActionKeys::Keys(_keys) = tooltip_shortcut {
if !shortcut_text.is_empty() {
if !tooltip.is_empty() {
tooltip.push(' ');
@ -279,12 +277,12 @@ impl WidgetHolder {
#[derive(Clone)]
pub struct WidgetCallback<T> {
pub callback: Rc<dyn Fn(&T) -> Message + 'static>,
pub callback: Arc<dyn Fn(&T) -> Message + 'static + Send + Sync>,
}
impl<T> WidgetCallback<T> {
pub fn new(callback: impl Fn(&T) -> Message + 'static) -> Self {
Self { callback: Rc::new(callback) }
pub fn new(callback: impl Fn(&T) -> Message + 'static + Send + Sync) -> Self {
Self { callback: Arc::new(callback) }
}
}

View file

@ -48,7 +48,7 @@ impl MenuBarEntry {
}
}
pub fn create_action(callback: impl Fn(&()) -> Message + 'static) -> WidgetHolder {
pub fn create_action(callback: impl Fn(&()) -> Message + 'static + Send + Sync) -> WidgetHolder {
WidgetHolder::new(Widget::InvisibleStandinInput(InvisibleStandinInput {
on_update: WidgetCallback::new(callback),
}))

View file

@ -548,7 +548,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
}
.into(),
);
let image_data = std::rc::Rc::new(image_data);
let image_data = std::sync::Arc::new(image_data);
responses.push_back(
FrontendMessage::UpdateImageData {
document_id,

View file

@ -162,7 +162,7 @@ impl MessageHandler<NavigationMessage, (&Document, &InputPreprocessorMessageHand
responses.push_back(
FrontendMessage::UpdateInputHints {
hint_data: HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control])],
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),
@ -244,7 +244,7 @@ impl MessageHandler<NavigationMessage, (&Document, &InputPreprocessorMessageHand
responses.push_back(
FrontendMessage::UpdateInputHints {
hint_data: HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control])],
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap Increments"),

View file

@ -18,6 +18,8 @@ pub enum FrontendGraphDataType {
Raster,
#[serde(rename = "color")]
Color,
#[serde(rename = "text")]
Text,
#[serde(rename = "vector")]
Subpath,
#[serde(rename = "number")]

View file

@ -2,14 +2,11 @@ use super::{node_properties, FrontendGraphDataType, FrontendNodeType};
use crate::messages::layout::utility_types::layout_widget::{LayoutGroup, Widget, WidgetHolder};
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use glam::DVec2;
use graph_craft::concrete;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graph_craft::proto::{NodeIdentifier, Type};
use graphene_std::raster::Image;
use graphene_std::vector::subpath::Subpath;
use std::borrow::Cow;
use graphene_core::raster::Image;
pub struct DocumentInputType {
pub name: &'static str,
@ -39,7 +36,7 @@ static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[
DocumentNodeType {
name: "Identity",
category: "General",
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[concrete!("Any<'_>")]),
inputs: &[DocumentInputType {
name: "In",
data_type: FrontendGraphDataType::General,
@ -58,7 +55,7 @@ static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[
DocumentNodeType {
name: "Input",
category: "Meta",
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[concrete!("Any<'_>")]),
inputs: &[DocumentInputType {
name: "In",
data_type: FrontendGraphDataType::Raster,
@ -70,7 +67,7 @@ static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[
DocumentNodeType {
name: "Output",
category: "Meta",
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[concrete!("Any<'_>")]),
inputs: &[DocumentInputType {
name: "In",
data_type: FrontendGraphDataType::Raster,
@ -87,6 +84,21 @@ static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[
outputs: &[FrontendGraphDataType::Raster],
properties: node_properties::no_properties,
},
DocumentNodeType {
name: "GpuImage",
category: "Image Adjustments",
identifier: NodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode", &[concrete!("&TypeErasedNode")]),
inputs: &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType {
name: "Path",
data_type: FrontendGraphDataType::Text,
default: NodeInput::value(TaggedValue::String(String::new()), true),
},
],
outputs: &[FrontendGraphDataType::Raster],
properties: node_properties::gpu_map_properties,
},
DocumentNodeType {
name: "Invert RGB",
category: "Image Adjustments",
@ -98,7 +110,7 @@ static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[
DocumentNodeType {
name: "Hue/Saturation",
category: "Image Adjustments",
identifier: NodeIdentifier::new("graphene_std::raster::HueSaturationNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
identifier: NodeIdentifier::new("graphene_std::raster::HueSaturationNode", &[concrete!("&TypeErasedNode")]),
inputs: &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Hue Shift", TaggedValue::F64(0.), false),
@ -111,7 +123,7 @@ static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[
DocumentNodeType {
name: "Brightness/Contrast",
category: "Image Adjustments",
identifier: NodeIdentifier::new("graphene_std::raster::BrightnessContrastNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
identifier: NodeIdentifier::new("graphene_std::raster::BrightnessContrastNode", &[concrete!("&TypeErasedNode")]),
inputs: &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Brightness", TaggedValue::F64(0.), false),
@ -123,7 +135,7 @@ static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[
DocumentNodeType {
name: "Gamma",
category: "Image Adjustments",
identifier: NodeIdentifier::new("graphene_std::raster::GammaNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
identifier: NodeIdentifier::new("graphene_std::raster::GammaNode", &[concrete!("&TypeErasedNode")]),
inputs: &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Gamma", TaggedValue::F64(1.), false),
@ -134,7 +146,7 @@ static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[
DocumentNodeType {
name: "Opacity",
category: "Image Adjustments",
identifier: NodeIdentifier::new("graphene_std::raster::OpacityNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
identifier: NodeIdentifier::new("graphene_std::raster::OpacityNode", &[concrete!("&TypeErasedNode")]),
inputs: &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Factor", TaggedValue::F64(1.), false),
@ -145,7 +157,7 @@ static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[
DocumentNodeType {
name: "Posterize",
category: "Image Adjustments",
identifier: NodeIdentifier::new("graphene_std::raster::PosterizeNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
identifier: NodeIdentifier::new("graphene_std::raster::PosterizeNode", &[concrete!("&TypeErasedNode")]),
inputs: &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Value", TaggedValue::F64(5.), false),
@ -156,7 +168,7 @@ static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[
DocumentNodeType {
name: "Exposure",
category: "Image Adjustments",
identifier: NodeIdentifier::new("graphene_std::raster::ExposureNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
identifier: NodeIdentifier::new("graphene_std::raster::ExposureNode", &[concrete!("&TypeErasedNode")]),
inputs: &[
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Value", TaggedValue::F64(0.), false),
@ -167,7 +179,7 @@ static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[
DocumentNodeType {
name: "Add",
category: "Math",
identifier: NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
identifier: NodeIdentifier::new("graphene_core::ops::AddNode", &[concrete!("&TypeErasedNode")]),
inputs: &[
DocumentInputType::new("Input", TaggedValue::F64(0.), true),
DocumentInputType::new("Addend", TaggedValue::F64(0.), true),
@ -194,7 +206,7 @@ static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[
DocumentNodeType {
name: "Path Generator",
category: "Vector",
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[concrete!("Any<'_>")]),
inputs: &[DocumentInputType {
name: "Path Data",
data_type: FrontendGraphDataType::Subpath,

View file

@ -1,6 +1,6 @@
use crate::messages::layout::utility_types::layout_widget::{LayoutGroup, Widget, WidgetCallback, WidgetHolder};
use crate::messages::layout::utility_types::widgets::button_widgets::ParameterExposeButton;
use crate::messages::layout::utility_types::widgets::input_widgets::{NumberInput, NumberInputMode};
use crate::messages::layout::utility_types::widgets::input_widgets::{NumberInput, NumberInputMode, TextInput};
use crate::messages::prelude::NodeGraphMessage;
use glam::DVec2;
@ -14,7 +14,7 @@ pub fn string_properties(text: impl Into<String>) -> Vec<LayoutGroup> {
vec![LayoutGroup::Row { widgets: vec![widget] }]
}
fn update_value<T, F: Fn(&T) -> TaggedValue + 'static>(value: F, node_id: NodeId, input_index: usize) -> WidgetCallback<T> {
fn update_value<T, F: Fn(&T) -> TaggedValue + 'static + Send + Sync>(value: F, node_id: NodeId, input_index: usize) -> WidgetCallback<T> {
WidgetCallback::new(move |number_input: &T| {
NodeGraphMessage::SetInputValue {
node: node_id,
@ -42,6 +42,32 @@ fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphDataType
}))
}
fn text_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str) -> Vec<WidgetHolder> {
let input: &NodeInput = document_node.inputs.get(index).unwrap();
let mut widgets = vec![
expose_widget(node_id, index, FrontendGraphDataType::Number, input.is_exposed()),
WidgetHolder::unrelated_seperator(),
WidgetHolder::text_widget(name),
];
if let NodeInput::Value {
tagged_value: TaggedValue::String(x),
exposed: false,
} = &document_node.inputs[index]
{
widgets.extend_from_slice(&[
WidgetHolder::unrelated_seperator(),
WidgetHolder::new(Widget::TextInput(TextInput {
value: x.clone(),
on_update: update_value(|x: &TextInput| TaggedValue::String(x.value.clone()), node_id, index),
..TextInput::default()
})),
])
}
widgets
}
fn number_range_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, range_min: Option<f64>, range_max: Option<f64>, unit: String, is_integer: bool) -> Vec<WidgetHolder> {
let input: &NodeInput = document_node.inputs.get(index).unwrap();
@ -98,6 +124,12 @@ pub fn adjust_gamma_properties(document_node: &DocumentNode, node_id: NodeId) ->
vec![LayoutGroup::Row { widgets: gamma }]
}
pub fn gpu_map_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
let map = text_widget(document_node, node_id, 1, "Map");
vec![LayoutGroup::Row { widgets: map }]
}
pub fn multiply_opacity(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
let gamma = number_range_widget(document_node, node_id, 1, "Factor", Some(0.), Some(1.), "".into(), false);

View file

@ -21,7 +21,7 @@ use graphene::layers::text_layer::{FontCache, TextLayer};
use glam::{DAffine2, DVec2};
use std::f64::consts::PI;
use std::rc::Rc;
use std::sync::Arc;
pub fn apply_transform_operation(layer: &Layer, transform_op: TransformOp, value: f64, font_cache: &FontCache) -> [f64; 6] {
let transformation = match transform_op {
@ -1457,7 +1457,7 @@ fn node_gradient_type(gradient: &Gradient) -> LayoutGroup {
}
fn node_gradient_color(gradient: &Gradient, position: usize) -> LayoutGroup {
let gradient_clone = Rc::new(gradient.clone());
let gradient_clone = Arc::new(gradient.clone());
let gradient_2 = gradient_clone.clone();
let gradient_3 = gradient_clone.clone();
let send_fill_message = move |new_gradient: Gradient| PropertiesPanelMessage::ModifyFill { fill: Fill::Gradient(new_gradient) }.into();

View file

@ -7,7 +7,7 @@ use glam::{DAffine2, DVec2};
use serde::ser::SerializeStruct;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RawBuffer(Vec<u8>);
impl From<Vec<u64>> for RawBuffer {
@ -22,8 +22,15 @@ impl From<Vec<u64>> for RawBuffer {
Self(v_from_raw)
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct JsRawBuffer(Vec<u8>);
impl Serialize for RawBuffer {
impl From<RawBuffer> for JsRawBuffer {
fn from(buffer: RawBuffer) -> Self {
Self(buffer.0)
}
}
impl Serialize for JsRawBuffer {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut buffer = serializer.serialize_struct("Buffer", 2)?;
buffer.serialize_field("pointer", &(self.0.as_ptr() as usize))?;

View file

@ -420,12 +420,12 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
size,
} => {
fn read_image(document: Option<&DocumentMessageHandler>, layer_path: &[LayerId], image_data: Vec<u8>, (width, height): (u32, u32)) -> Result<Vec<u8>, String> {
use graphene_std::raster::Image;
use graphene_core::raster::Image;
use image::{ImageBuffer, Rgba};
use std::io::Cursor;
let data = image_data.chunks_exact(4).map(|v| graphene_core::raster::color::Color::from_rgba8(v[0], v[1], v[2], v[3])).collect();
let image = graphene_std::raster::Image { width, height, data };
let image = graphene_core::raster::Image { width, height, data };
let document = document.ok_or_else(|| "Invalid document".to_string())?;
let layer = document.graphene_document.layer(layer_path).map_err(|e| format!("No layer: {e:?}"))?;
@ -452,7 +452,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?");
for (_id, node) in proto_network.nodes {
info!("Inserting proto node {:?}", node);
graph_craft::node_registry::push_node(node, &stack);
interpreted_executor::node_registry::push_node(node, &stack);
}
use borrow_stack::BorrowStack;
@ -484,7 +484,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
.into(),
);
let mime = "image/bmp".to_string();
let image_data = std::rc::Rc::new(image_data);
let image_data = std::sync::Arc::new(image_data);
responses.push_back(
FrontendMessage::UpdateImageData {
document_id,

View file

@ -453,7 +453,7 @@ impl Fsm for ArtboardToolFsmState {
plus: false,
}]),
HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Backspace])],
key_groups: vec![KeysGroup(vec![Key::Backspace]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Delete Artboard"),
@ -461,7 +461,7 @@ impl Fsm for ArtboardToolFsmState {
}]),
]),
ArtboardToolFsmState::Dragging => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain to Axis"),
@ -469,14 +469,14 @@ impl Fsm for ArtboardToolFsmState {
}])]),
ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Square"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt])],
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),

View file

@ -191,14 +191,14 @@ impl Fsm for EllipseToolFsmState {
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Circular"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt])],
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
@ -207,14 +207,14 @@ impl Fsm for EllipseToolFsmState {
])]),
EllipseToolFsmState::Drawing => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Circular"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt])],
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),

View file

@ -178,7 +178,7 @@ impl Fsm for EyedropperToolFsmState {
},
])]),
EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Escape])],
key_groups: vec![KeysGroup(vec![Key::Escape]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Cancel"),

View file

@ -518,7 +518,7 @@ impl Fsm for GradientToolFsmState {
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),
@ -526,7 +526,7 @@ impl Fsm for GradientToolFsmState {
},
])]),
GradientToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),

View file

@ -190,14 +190,14 @@ impl Fsm for ImaginateToolFsmState {
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Square"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt])],
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
@ -206,14 +206,14 @@ impl Fsm for ImaginateToolFsmState {
])]),
ImaginateToolFsmState::Drawing => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Square"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt])],
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),

View file

@ -247,21 +247,21 @@ impl Fsm for LineToolFsmState {
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt])],
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control])],
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Lock Angle"),
@ -270,21 +270,21 @@ impl Fsm for LineToolFsmState {
])]),
LineToolFsmState::Drawing => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt])],
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control])],
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Lock Angle"),

View file

@ -201,7 +201,7 @@ impl Fsm for NavigateToolFsmState {
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Zoom Out"),
@ -217,7 +217,7 @@ impl Fsm for NavigateToolFsmState {
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control])],
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap Increments"),
@ -240,7 +240,7 @@ impl Fsm for NavigateToolFsmState {
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control])],
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),
@ -249,14 +249,14 @@ impl Fsm for NavigateToolFsmState {
]),
]),
NavigateToolFsmState::Tilting => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control])],
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),
plus: false,
}])]),
NavigateToolFsmState::Zooming => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control])],
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap Increments"),

View file

@ -190,14 +190,14 @@ impl Fsm for NodeGraphToolFsmState {
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Square"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt])],
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
@ -206,14 +206,14 @@ impl Fsm for NodeGraphToolFsmState {
])]),
NodeGraphToolFsmState::Drawing => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Square"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt])],
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),

View file

@ -322,7 +322,7 @@ impl Fsm for PathToolFsmState {
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Grow/Shrink Selection"),
@ -339,10 +339,10 @@ impl Fsm for PathToolFsmState {
HintGroup(vec![
HintInfo {
key_groups: vec![
KeysGroup(vec![Key::ArrowUp]),
KeysGroup(vec![Key::ArrowRight]),
KeysGroup(vec![Key::ArrowDown]),
KeysGroup(vec![Key::ArrowLeft]),
KeysGroup(vec![Key::ArrowUp]).into(),
KeysGroup(vec![Key::ArrowRight]).into(),
KeysGroup(vec![Key::ArrowDown]).into(),
KeysGroup(vec![Key::ArrowLeft]).into(),
],
key_groups_mac: None,
mouse: None,
@ -350,7 +350,7 @@ impl Fsm for PathToolFsmState {
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Big Increment Nudge"),
@ -359,21 +359,21 @@ impl Fsm for PathToolFsmState {
]),
HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::KeyG])],
key_groups: vec![KeysGroup(vec![Key::KeyG]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Grab Selected (coming soon)"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::KeyR])],
key_groups: vec![KeysGroup(vec![Key::KeyR]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Rotate Selected (coming soon)"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::KeyS])],
key_groups: vec![KeysGroup(vec![Key::KeyS]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Scale Selected (coming soon)"),
@ -383,14 +383,14 @@ impl Fsm for PathToolFsmState {
]),
PathToolFsmState::Dragging => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt])],
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Split/Align Handles (Toggle)"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Share Lengths of Aligned Handles"),

View file

@ -636,21 +636,21 @@ impl Fsm for PenToolFsmState {
plus: false,
}]),
HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control])],
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),
plus: false,
}]),
HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Break Handle"),
plus: false,
}]),
HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Enter])],
key_groups: vec![KeysGroup(vec![Key::Enter]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("End Path"),

View file

@ -192,14 +192,14 @@ impl Fsm for RectangleToolFsmState {
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Square"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt])],
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
@ -208,14 +208,14 @@ impl Fsm for RectangleToolFsmState {
])]),
RectangleToolFsmState::Drawing => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Square"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt])],
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),

View file

@ -793,21 +793,21 @@ impl Fsm for SelectToolFsmState {
}]),
HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::KeyG])],
key_groups: vec![KeysGroup(vec![Key::KeyG]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Grab Selected"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::KeyR])],
key_groups: vec![KeysGroup(vec![Key::KeyR]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Rotate Selected"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::KeyS])],
key_groups: vec![KeysGroup(vec![Key::KeyS]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Scale Selected"),
@ -823,14 +823,14 @@ impl Fsm for SelectToolFsmState {
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control])],
key_groups_mac: Some(vec![KeysGroup(vec![Key::Command])]),
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: Some(vec![KeysGroup(vec![Key::Command]).into()]),
mouse: None,
label: String::from("Innermost"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Grow/Shrink Selection"),
@ -846,7 +846,7 @@ impl Fsm for SelectToolFsmState {
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Grow/Shrink Selection"),
@ -856,10 +856,10 @@ impl Fsm for SelectToolFsmState {
HintGroup(vec![
HintInfo {
key_groups: vec![
KeysGroup(vec![Key::ArrowUp]),
KeysGroup(vec![Key::ArrowRight]),
KeysGroup(vec![Key::ArrowDown]),
KeysGroup(vec![Key::ArrowLeft]),
KeysGroup(vec![Key::ArrowUp]).into(),
KeysGroup(vec![Key::ArrowRight]).into(),
KeysGroup(vec![Key::ArrowDown]).into(),
KeysGroup(vec![Key::ArrowLeft]).into(),
],
key_groups_mac: None,
mouse: None,
@ -867,7 +867,7 @@ impl Fsm for SelectToolFsmState {
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Big Increment Nudge"),
@ -876,15 +876,15 @@ impl Fsm for SelectToolFsmState {
]),
HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt])],
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Move Duplicate"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control, Key::KeyD])],
key_groups_mac: Some(vec![KeysGroup(vec![Key::Command, Key::KeyD])]),
key_groups: vec![KeysGroup(vec![Key::Control, Key::KeyD]).into()],
key_groups_mac: Some(vec![KeysGroup(vec![Key::Command, Key::KeyD]).into()]),
mouse: None,
label: String::from("Duplicate"),
plus: false,
@ -893,14 +893,14 @@ impl Fsm for SelectToolFsmState {
]),
SelectToolFsmState::Dragging => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain to Axis"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control])],
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap to Points (coming soon)"),
@ -910,7 +910,7 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::DrawingBox => HintData(vec![]),
SelectToolFsmState::ResizingBounds => HintData(vec![]),
SelectToolFsmState::RotatingBounds => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control])],
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),

View file

@ -235,14 +235,14 @@ impl Fsm for ShapeToolFsmState {
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain 1:1 Aspect"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt])],
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
@ -251,14 +251,14 @@ impl Fsm for ShapeToolFsmState {
])]),
ShapeToolFsmState::Drawing => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift])],
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain 1:1 Aspect"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt])],
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),

View file

@ -267,7 +267,7 @@ impl Fsm for SplineToolFsmState {
plus: false,
}]),
HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Enter])],
key_groups: vec![KeysGroup(vec![Key::Enter]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("End Spline"),

View file

@ -474,14 +474,14 @@ impl Fsm for TextToolFsmState {
])]),
TextToolFsmState::Editing => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control, Key::Enter])],
key_groups_mac: Some(vec![KeysGroup(vec![Key::Command, Key::Enter])]),
key_groups: vec![KeysGroup(vec![Key::Control, Key::Enter]).into()],
key_groups_mac: Some(vec![KeysGroup(vec![Key::Command, Key::Enter]).into()]),
mouse: None,
label: String::from("Commit Edit"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Escape])],
key_groups: vec![KeysGroup(vec![Key::Escape]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Discard Edit"),

View file

@ -1,5 +1,5 @@
use super::tool_messages::*;
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
use crate::messages::input_mapper::utility_types::input_keyboard::LayoutKeysGroup;
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
@ -21,7 +21,7 @@ pub type ToolActionHandlerData<'a> = (&'a DocumentMessageHandler, u64, &'a Docum
pub trait ToolCommon: for<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> + PropertyHolder + ToolTransition + ToolMetadata {}
impl<T> ToolCommon for T where T: for<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> + PropertyHolder + ToolTransition + ToolMetadata {}
type Tool = dyn ToolCommon;
type Tool = dyn ToolCommon + Send + Sync;
pub trait Fsm {
type ToolData;
@ -442,10 +442,10 @@ pub struct HintInfo {
/// A `KeysGroup` specifies all the keys pressed simultaneously to perform an action (like "Ctrl C" to copy).
/// Usually at most one is given, but less commonly, multiple can be used to describe additional hotkeys not used simultaneously (like the four different arrow keys to nudge a layer).
#[serde(rename = "keyGroups")]
pub key_groups: Vec<KeysGroup>,
pub key_groups: Vec<LayoutKeysGroup>,
/// `None` means that the regular `key_groups` should be used for all platforms, `Some` is an override for a Mac-only input hint.
#[serde(rename = "keyGroupsMac")]
pub key_groups_mac: Option<Vec<KeysGroup>>,
pub key_groups_mac: Option<Vec<LayoutKeysGroup>>,
/// An optional `MouseMotion` that can indicate the mouse action, like which mouse button is used and whether a drag occurs.
/// No such icon is shown if `None` is given, and it can be combined with `key_groups` if desired.
pub mouse: Option<MouseMotion>,

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,9 @@
"serve": "vue-cli-service serve || echo 'Graphite project failed to build. Did you remember to `npm install` the dependencies?'",
"build": "vue-cli-service build || echo 'Graphite project failed to build. Did you remember to `npm install` the dependencies?'",
"lint": "vue-cli-service lint || echo 'Graphite project had lint errors or otherwise failed. In the latter case, did you remember to `npm install` the dependencies?'",
"lint-no-fix": "vue-cli-service lint --no-fix || echo 'Graphite project had lint errors or otherwise failed. In the latter case, did you remember to `npm install` the dependencies?'"
"lint-no-fix": "vue-cli-service lint --no-fix || echo 'Graphite project had lint errors or otherwise failed. In the latter case, did you remember to `npm install` the dependencies?'",
"tauri:build": "vue-cli-service tauri:build",
"tauri:serve": "vue-cli-service tauri:serve"
},
"repository": {
"type": "git",
@ -17,6 +19,7 @@
"license": "Apache-2.0",
"homepage": "https://graphite.rs",
"dependencies": {
"@tauri-apps/api": "^1.2.0",
"class-transformer": "^0.5.1",
"idb-keyval": "^6.2.0",
"reflect-metadata": "^0.1.13",
@ -42,6 +45,7 @@
"sass": "^1.56.1",
"sass-loader": "^13.2.0",
"typescript": "^4.9.3",
"vue-cli-plugin-tauri": "~1.0.0",
"vue-loader": "^17.0.1",
"vue-template-compiler": "^2.7.14"
},

3
frontend/src-tauri/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Generated by Cargo
# will have compiled files and executables
/target/

View file

@ -0,0 +1,38 @@
[package]
name = "graphite-desktop"
version = "0.1.0"
description = "Graphite Desktop"
authors = ["Graphite Authors <contact@graphite.rs>"]
license = "Apache-2.0"
repository = ""
default-run = "graphite-desktop"
edition = "2021"
rust-version = "1.59"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.2", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.2", features = ["api-all", "devtools"] }
axum = "0.6.1"
graphite-editor = { version = "0.0.0", path = "../../editor" }
chrono = "^0.4.23"
ron = "0.8"
log = "0.4"
fern = {version = "0.6", features = ["colored"] }
futures = "0.3.25"
[features]
gpu = ["graphite-editor/gpu"]
# by default Tauri runs in production mode
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
default = [ "custom-protocol" ]
# this feature is used for production builds where `devPath` points to the filesystem
# DO NOT remove this
custom-protocol = [ "tauri/custom-protocol" ]

View file

@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -0,0 +1,125 @@
#![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")]
use std::sync::Arc;
use axum::body::StreamBody;
use axum::extract::Path;
use axum::http;
use axum::response::IntoResponse;
use axum::{routing::get, Router};
use fern::colors::{Color, ColoredLevelConfig};
use graphite_editor::application::Editor;
use graphite_editor::messages::frontend::utility_types::FrontendImageData;
use graphite_editor::messages::prelude::*;
use http::{Response, StatusCode};
use std::collections::HashMap;
use std::sync::Mutex;
use tauri::Manager;
static IMAGES: Mutex<Option<HashMap<String, FrontendImageData>>> = Mutex::new(None);
static EDITOR: Mutex<Option<Editor>> = Mutex::new(None);
async fn respond_to(id: Path<String>) -> impl IntoResponse {
let builder = Response::builder().header("Access-Control-Allow-Origin", "*").status(StatusCode::OK);
let guard = IMAGES.lock().unwrap();
let images = guard;
let image = images.as_ref().unwrap().get(&id.0).unwrap();
println!("image: {:#?}", image.path);
let result: Result<Vec<u8>, &str> = Ok((*image.image_data).clone());
let stream = futures::stream::once(async move { result });
builder.body(StreamBody::new(stream)).unwrap()
}
fn main() {
println!("Starting server...");
let colors = ColoredLevelConfig::new().debug(Color::Magenta).info(Color::Green).error(Color::Red);
fern::Dispatch::new()
.chain(std::io::stdout())
.level(log::LevelFilter::Trace)
.format(move |out, message, record| {
out.finish(format_args!(
"[{}]{} {}",
// This will color the log level only, not the whole line. Just a touch.
colors.color(record.level()),
chrono::Utc::now().format("[%Y-%m-%d %H:%M:%S]"),
message
))
})
.apply()
.unwrap();
*(IMAGES.lock().unwrap()) = Some(HashMap::new());
graphite_editor::application::set_uuid_seed(0);
*(EDITOR.lock().unwrap()) = Some(Editor::new());
let app = Router::new().route("/", get(|| async { "Hello, World!" })).route("/image/:id", get(respond_to));
// run it with hyper on localhost:3000
tauri::async_runtime::spawn(async {
axum::Server::bind(&"0.0.0.0:3001".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
});
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![set_random_seed, handle_message])
.setup(|app| {
app.get_window("main").unwrap().open_devtools();
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[tauri::command]
fn set_random_seed(seed: f64) {
let seed = seed as u64;
graphite_editor::application::set_uuid_seed(seed);
}
#[tauri::command]
fn handle_message(message: String) -> String {
let Ok(message) = ron::from_str::<graphite_editor::messages::message::Message>(&message) else {
panic!("Error parsing message: {}", message)
};
let mut guard = EDITOR.lock().unwrap();
let editor = (*guard).as_mut().unwrap();
let responses = editor.handle_message(message);
// Sends a FrontendMessage to JavaScript
fn send_frontend_message_to_js(message: FrontendMessage) -> FrontendMessage {
// Special case for update image data to avoid serialization times.
if let FrontendMessage::UpdateImageData { document_id, image_data } = message {
let mut guard = IMAGES.lock().unwrap();
let images = (*guard).as_mut().unwrap();
let mut stub_data = Vec::with_capacity(image_data.len());
for image in image_data {
let path = image.path.clone();
let mime = image.mime.clone();
images.insert(format!("{:?}_{}", &image.path, document_id), image);
stub_data.push(FrontendImageData {
path,
mime,
image_data: Arc::new(Vec::new()),
});
}
FrontendMessage::UpdateImageData { document_id, image_data: stub_data }
} else {
message
}
}
for response in &responses {
let serialized = ron::to_string(&send_frontend_message_to_js(response.clone())).unwrap();
if let Err(error) = ron::from_str::<FrontendMessage>(&serialized) {
log::error!("Error deserializing message: {}", error);
log::debug!("{:#?}", response);
log::debug!("{}", serialized);
}
}
// Process any `FrontendMessage` responses resulting from the backend processing the dispatched message
let result: Vec<_> = responses.into_iter().map(send_frontend_message_to_js).collect();
ron::to_string(&result).expect("Failed to serialize FrontendMessage")
}

View file

@ -0,0 +1,66 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm start",
"distDir": "../dist",
"devPath": "http://127.0.0.1:8080"
},
"package": {
"productName": "graphite-tauri",
"version": "0.1.0"
},
"tauri": {
"allowlist": {
"all": true
},
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "",
"deb": {
"depends": ["librustc_codegen_spirv"]
},
"externalBin": [],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"identifier": "rs.graphite.editor",
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"security": {
"csp": null
},
"updater": {
"active": false
},
"windows": [
{
"fullscreen": false,
"height": 600,
"resizable": true,
"title": "Graphite",
"width": 800
}
]
}
}

View file

@ -280,7 +280,7 @@ import {
type LayerPanelEntry,
defaultWidgetLayout,
UpdateDocumentLayerDetails,
UpdateDocumentLayerTreeStructure,
UpdateDocumentLayerTreeStructureJs,
UpdateLayerTreeOptionsLayout,
layerTypeData,
} from "@/wasm-communication/messages";
@ -483,14 +483,14 @@ export default defineComponent({
this.fakeHighlight = undefined;
this.dragInPanel = false;
},
rebuildLayerTree(updateDocumentLayerTreeStructure: UpdateDocumentLayerTreeStructure) {
rebuildLayerTree(updateDocumentLayerTreeStructure: UpdateDocumentLayerTreeStructureJs) {
const layerWithNameBeingEdited = this.layers.find((layer: LayerListingInfo) => layer.editingName);
const layerPathWithNameBeingEdited = layerWithNameBeingEdited?.entry.path;
const layerIdWithNameBeingEdited = layerPathWithNameBeingEdited?.slice(-1)[0];
const path = [] as bigint[];
this.layers = [] as LayerListingInfo[];
const recurse = (folder: UpdateDocumentLayerTreeStructure, layers: LayerListingInfo[], cache: Map<string, LayerPanelEntry>): void => {
const recurse = (folder: UpdateDocumentLayerTreeStructureJs, layers: LayerListingInfo[], cache: Map<string, LayerPanelEntry>): void => {
folder.children.forEach((item, index) => {
// TODO: fix toString
const layerId = BigInt(item.layerId.toString());
@ -520,7 +520,7 @@ export default defineComponent({
},
},
mounted() {
this.editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerTreeStructure, (updateDocumentLayerTreeStructure) => {
this.editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerTreeStructureJs, (updateDocumentLayerTreeStructure) => {
this.rebuildLayerTree(updateDocumentLayerTreeStructure);
});

View file

@ -68,7 +68,7 @@
import { defineComponent } from "vue";
import { platformIsMac } from "@/utility-functions/platform";
import { type KeyRaw, type KeysGroup, type MenuBarEntry, type MenuListEntry, UpdateMenuBarLayout } from "@/wasm-communication/messages";
import { type KeyRaw, type LayoutKeysGroup, type MenuBarEntry, type MenuListEntry, UpdateMenuBarLayout } from "@/wasm-communication/messages";
import MenuList from "@/components/floating-menus/MenuList.vue";
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
@ -92,7 +92,7 @@ export default defineComponent({
mounted() {
this.editor.subscriptions.subscribeJsMessage(UpdateMenuBarLayout, (updateMenuBarLayout) => {
const arraysEqual = (a: KeyRaw[], b: KeyRaw[]): boolean => a.length === b.length && a.every((aValue, i) => aValue === b[i]);
const shortcutRequiresLock = (shortcut: KeysGroup): boolean => {
const shortcutRequiresLock = (shortcut: LayoutKeysGroup): boolean => {
const shortcutKeys = shortcut.map((keyWithLabel) => keyWithLabel.key);
// If this shortcut matches any of the browser-reserved shortcuts

View file

@ -127,7 +127,7 @@ import { defineComponent, type PropType } from "vue";
import { type IconName } from "@/utility-functions/icons";
import { platformIsMac } from "@/utility-functions/platform";
import { type KeyRaw, type KeysGroup, type Key, type MouseMotion } from "@/wasm-communication/messages";
import { type KeyRaw, type LayoutKeysGroup, type Key, type MouseMotion } from "@/wasm-communication/messages";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
@ -158,7 +158,7 @@ const ICON_WIDTHS = {
export default defineComponent({
inject: ["fullscreen"],
props: {
keysWithLabelsGroups: { type: Array as PropType<KeysGroup[]>, default: () => [] },
keysWithLabelsGroups: { type: Array as PropType<LayoutKeysGroup[]>, default: () => [] },
mouseMotion: { type: String as PropType<MouseMotion | undefined>, required: false },
requiresLock: { type: Boolean as PropType<boolean>, default: false },
},
@ -182,7 +182,7 @@ export default defineComponent({
},
},
methods: {
keyTextOrIconList(keyGroup: KeysGroup): LabelData[] {
keyTextOrIconList(keyGroup: LayoutKeysGroup): LabelData[] {
return keyGroup.map((key) => this.keyTextOrIcon(key));
},
keyTextOrIcon(keyWithLabel: Key): LabelData {

View file

@ -49,7 +49,7 @@
import { defineComponent } from "vue";
import { platformIsMac } from "@/utility-functions/platform";
import { type HintData, type HintInfo, type KeysGroup, UpdateInputHints } from "@/wasm-communication/messages";
import { type HintData, type HintInfo, type LayoutKeysGroup, UpdateInputHints } from "@/wasm-communication/messages";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import Separator from "@/components/widgets/labels/Separator.vue";
@ -63,7 +63,7 @@ export default defineComponent({
};
},
methods: {
inputKeysForPlatform(hint: HintInfo): KeysGroup[] {
inputKeysForPlatform(hint: HintInfo): LayoutKeysGroup[] {
if (platformIsMac() && hint.keyGroupsMac) return hint.keyGroupsMac;
return hint.keyGroups;
},

View file

@ -214,7 +214,7 @@ import { defineComponent, nextTick, type PropType } from "vue";
import { platformIsMac } from "@/utility-functions/platform";
import { type KeysGroup, type Key } from "@/wasm-communication/messages";
import { type LayoutKeysGroup, type Key } from "@/wasm-communication/messages";
import LayoutCol from "@/components/layout/LayoutCol.vue";
import LayoutRow from "@/components/layout/LayoutRow.vue";
@ -258,7 +258,7 @@ export default defineComponent({
openDocument() {
this.editor.instance.documentOpen();
},
platformModifiers(reservedKey: boolean): KeysGroup {
platformModifiers(reservedKey: boolean): LayoutKeysGroup {
// TODO: Remove this by properly feeding these keys from a layout provided by the backend
const ALT: Key = { key: "Alt", label: "Alt" };

View file

@ -30,7 +30,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const listeners: { target: EventListenerTarget; eventName: EventName; action: (event: any) => void; options?: boolean | AddEventListenerOptions }[] = [
{ target: window, eventName: "resize", action: (): void => onWindowResize(container) },
{ target: window, eventName: "beforeunload", action: (e: BeforeUnloadEvent): void => onBeforeUnload(e) },
{ target: window, eventName: "beforeunload", action: (e: BeforeUnloadEvent): Promise<void> => onBeforeUnload(e) },
{ target: window.document, eventName: "contextmenu", action: (e: MouseEvent): void => e.preventDefault() },
{ target: window.document, eventName: "fullscreenchange", action: (): void => fullscreen.fullscreenModeChanged() },
{ target: window, eventName: "keyup", action: (e: KeyboardEvent): Promise<void> => onKeyUp(e) },
@ -235,15 +235,15 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
if (boundsOfViewports.length > 0) editor.instance.boundsOfViewports(data);
}
function onBeforeUnload(e: BeforeUnloadEvent): void {
async function onBeforeUnload(e: BeforeUnloadEvent): Promise<void> {
const activeDocument = document.state.documents[document.state.activeDocumentIndex];
if (activeDocument && !activeDocument.isAutoSaved) editor.instance.triggerAutoSave(activeDocument.id);
// Skip the message if the editor crashed, since work is already lost
if (editor.instance.hasCrashed()) return;
if (await editor.instance.hasCrashed()) return;
// Skip the message during development, since it's annoying when testing
if (editor.instance.inDevelopmentMode()) return;
if (await editor.instance.inDevelopmentMode()) return;
const allDocumentsSaved = document.state.documents.reduce((acc, doc) => acc && doc.isSaved, true);
if (!allDocumentsSaved) {

View file

@ -1,3 +1,5 @@
import { invoke } from "@tauri-apps/api";
import type WasmBindgenPackage from "@/../wasm/pkg";
import { panicProxy } from "@/utility-functions/panic-proxy";
import { type JsMessageType } from "@/wasm-communication/messages";
@ -24,6 +26,30 @@ export async function updateImage(path: BigUint64Array, mime: string, imageData:
editorInstance?.setImageBlobURL(documentId, path, blobURL, image.naturalWidth, image.naturalHeight);
}
export async function fetchImage(path: BigUint64Array, mime: string, documentId: bigint, url: string): Promise<void> {
const data = await fetch(url);
const blob = await data.blob();
const blobURL = URL.createObjectURL(blob);
// Pre-decode the image so it is ready to be drawn instantly once it's placed into the viewport SVG
const image = new Image();
image.src = blobURL;
await image.decode();
editorInstance?.setImageBlobURL(documentId, path, blobURL, image.naturalWidth, image.naturalHeight);
}
// export async function dispatchTauri(message: string): Promise<string> {
export async function dispatchTauri(message: any): Promise<void> {
try {
const response = await invoke("handle_message", { message });
editorInstance?.tauriResponse(response);
} catch {
console.error("Failed to dispatch Tauri message");
}
}
// Should be called asynchronously before `createEditor()`
export async function initWasm(): Promise<void> {
// Skip if the WASM module is already initialized
@ -34,8 +60,14 @@ export async function initWasm(): Promise<void> {
wasmImport = await import("@/../wasm/pkg").then(panicProxy);
// Provide a random starter seed which must occur after initializing the WASM module, since WASM can't generate its own random numbers
const randomSeed = BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER));
const randomSeedFloat = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
const randomSeed = BigInt(randomSeedFloat);
wasmImport?.setRandomSeed(randomSeed);
try {
await invoke("set_random_seed", { seed: randomSeedFloat });
} catch {
// Ignore errors
}
}
// Should be called after running `initWasm()` and its promise resolving

View file

@ -136,9 +136,9 @@ export type HintData = HintGroup[];
export type HintGroup = HintInfo[];
export class HintInfo {
readonly keyGroups!: KeysGroup[];
readonly keyGroups!: LayoutKeysGroup[];
readonly keyGroupsMac!: KeysGroup[] | undefined;
readonly keyGroupsMac!: LayoutKeysGroup[] | undefined;
readonly mouse!: MouseMotion | undefined;
@ -151,8 +151,8 @@ export class HintInfo {
export type KeyRaw = string;
// Serde converts a Rust `Key` enum variant into this format (via a custom serializer) with both the `Key` variant name (called `RawKey` in TS) and the localized `label` for the key
export type Key = { key: KeyRaw; label: string };
export type KeysGroup = Key[];
export type ActionKeys = { keys: KeysGroup };
export type LayoutKeysGroup = Key[];
export type ActionKeys = { keys: LayoutKeysGroup };
export type MouseMotion = string;
@ -596,8 +596,8 @@ export class TriggerSavePreferences extends JsMessage {
export class DocumentChanged extends JsMessage {}
export class UpdateDocumentLayerTreeStructure extends JsMessage {
constructor(readonly layerId: bigint, readonly children: UpdateDocumentLayerTreeStructure[]) {
export class UpdateDocumentLayerTreeStructureJs extends JsMessage {
constructor(readonly layerId: bigint, readonly children: UpdateDocumentLayerTreeStructureJs[]) {
super();
}
}
@ -607,7 +607,7 @@ type DataBuffer = {
length: bigint;
};
export function newUpdateDocumentLayerTreeStructure(input: { dataBuffer: DataBuffer }, wasm: WasmRawInstance): UpdateDocumentLayerTreeStructure {
export function newUpdateDocumentLayerTreeStructure(input: { dataBuffer: DataBuffer }, wasm: WasmRawInstance): UpdateDocumentLayerTreeStructureJs {
const pointerNum = Number(input.dataBuffer.pointer);
const lengthNum = Number(input.dataBuffer.length);
@ -624,7 +624,7 @@ export function newUpdateDocumentLayerTreeStructure(input: { dataBuffer: DataBuf
const layerIdsSection = new DataView(wasmMemoryBuffer, pointerNum + 8 + structureSectionLength * 8);
let layersEncountered = 0;
let currentFolder = new UpdateDocumentLayerTreeStructure(BigInt(-1), []);
let currentFolder = new UpdateDocumentLayerTreeStructureJs(BigInt(-1), []);
const currentFolderStack = [currentFolder];
for (let i = 0; i < structureSectionLength; i += 1) {
@ -639,7 +639,7 @@ export function newUpdateDocumentLayerTreeStructure(input: { dataBuffer: DataBuf
const layerId = layerIdsSection.getBigUint64(layersEncountered * 8, true);
layersEncountered += 1;
const childLayer = new UpdateDocumentLayerTreeStructure(layerId, []);
const childLayer = new UpdateDocumentLayerTreeStructureJs(layerId, []);
currentFolder.children.push(childLayer);
}
@ -1361,7 +1361,7 @@ export const messageMakers: Record<string, MessageMaker> = {
UpdateDocumentArtwork,
UpdateDocumentBarLayout,
UpdateDocumentLayerDetails,
UpdateDocumentLayerTreeStructure: newUpdateDocumentLayerTreeStructure,
UpdateDocumentLayerTreeStructureJs: newUpdateDocumentLayerTreeStructure,
UpdateDocumentModeLayout,
UpdateDocumentOverlays,
UpdateDocumentRulers,

View file

@ -10,6 +10,10 @@ homepage = "https://graphite.rs"
repository = "https://github.com/GraphiteEditor/Graphite"
license = "Apache-2.0"
[features]
tauri = ["ron"]
default = []
[lib]
crate-type = ["cdylib", "rlib"]
@ -22,6 +26,8 @@ serde = { version = "1.0", features = ["derive"] }
wasm-bindgen = { version = "0.2.73" }
serde-wasm-bindgen = "0.4.1"
js-sys = "0.3.55"
wasm-bindgen-futures = "0.4.33"
ron = {version = "0.8", optional = true}
[dev-dependencies]
wasm-bindgen-test = "0.3.22"

View file

@ -33,6 +33,9 @@ pub fn set_random_seed(seed: u64) {
#[wasm_bindgen(module = "@/wasm-communication/editor")]
extern "C" {
fn updateImage(path: Vec<u64>, mime: String, imageData: &[u8], document_id: u64);
fn fetchImage(path: Vec<u64>, mime: String, document_id: u64, identifier: String);
//fn dispatchTauri(message: String) -> String;
fn dispatchTauri(message: String);
}
/// Provides a handle to access the raw WASM memory
@ -73,37 +76,53 @@ impl JsEditorHandle {
return;
}
// Get the editor instances, dispatch the message, and store the `FrontendMessage` queue response
let frontend_messages = EDITOR_INSTANCES.with(|instances| {
// Mutably borrow the editors, and if successful, we can access them in the closure
instances.try_borrow_mut().map(|mut editors| {
// Get the editor instance for this editor ID, then dispatch the message to the backend, and return its response `FrontendMessage` queue
editors
.get_mut(&self.editor_id)
.expect("EDITOR_INSTANCES does not contain the current editor_id")
.handle_message(message.into())
})
});
#[cfg(feature = "tauri")]
{
let message: Message = message.into();
let message = ron::to_string(&message).unwrap();
// Process any `FrontendMessage` responses resulting from the backend processing the dispatched message
if let Ok(frontend_messages) = frontend_messages {
// Send each `FrontendMessage` to the JavaScript frontend
for message in frontend_messages.into_iter() {
self.send_frontend_message_to_js(message);
dispatchTauri(message);
}
#[cfg(not(feature = "tauri"))]
{
// Get the editor instances, dispatch the message, and store the `FrontendMessage` queue response
let frontend_messages = EDITOR_INSTANCES.with(|instances| {
// Mutably borrow the editors, and if successful, we can access them in the closure
instances.try_borrow_mut().map(|mut editors| {
// Get the editor instance for this editor ID, then dispatch the message to the backend, and return its response `FrontendMessage` queue
editors
.get_mut(&self.editor_id)
.expect("EDITOR_INSTANCES does not contain the current editor_id")
.handle_message(message.into())
})
});
// Process any `FrontendMessage` responses resulting from the backend processing the dispatched message
if let Ok(frontend_messages) = frontend_messages {
// Send each `FrontendMessage` to the JavaScript frontend
for message in frontend_messages.into_iter() {
self.send_frontend_message_to_js(message);
}
}
}
// If the editor cannot be borrowed then it has encountered a panic - we should just ignore new dispatches
}
// Sends a FrontendMessage to JavaScript
fn send_frontend_message_to_js(&self, message: FrontendMessage) {
fn send_frontend_message_to_js(&self, mut message: FrontendMessage) {
// Special case for update image data to avoid serialization times.
if let FrontendMessage::UpdateImageData { document_id, image_data } = message {
for image in image_data {
#[cfg(not(feature = "tauri"))]
updateImage(image.path, image.mime, &image.image_data, document_id);
#[cfg(feature = "tauri")]
fetchImage(image.path.clone(), image.mime, document_id, format!("http://localhost:3001/image/{:?}_{}", &image.path, document_id));
}
return;
}
if let FrontendMessage::UpdateDocumentLayerTreeStructure { data_buffer } = message {
message = FrontendMessage::UpdateDocumentLayerTreeStructureJs { data_buffer: data_buffer.into() };
}
let message_type = message.to_discriminant().local_name();
@ -139,6 +158,21 @@ impl JsEditorHandle {
self.dispatch(Message::Init);
}
#[wasm_bindgen(js_name = tauriResponse)]
pub fn tauri_response(&self, message: JsValue) {
#[cfg(feature = "tauri")]
match ron::from_str::<Vec<FrontendMessage>>(&message.as_string().unwrap()) {
Ok(response) => {
for message in response {
self.send_frontend_message_to_js(message);
}
}
Err(error) => {
log::error!("tauri response: {:?}\n{:?}", error, message);
}
}
}
/// Displays a dialog with an error message
#[wasm_bindgen(js_name = errorDialog)]
pub fn error_dialog(&self, title: String, description: String) {

View file

@ -23,7 +23,7 @@ kurbo = { git = "https://github.com/linebender/kurbo.git", features = [
] }
serde = { version = "1.0", features = ["derive"] }
base64 = "0.13"
glam = { version = "0.17", features = ["serde"] }
glam = { version = "0.22", features = ["serde"] }
# Font rendering
rustybuzz = "*"

View file

@ -600,7 +600,7 @@ impl Document {
image_data,
mime,
} => {
let image_data = std::rc::Rc::new(image_data);
let image_data = std::sync::Arc::new(image_data);
let layer = Layer::new(LayerDataType::Image(ImageLayer::new(mime, image_data)), transform);
self.set_layer(&path, layer, insert_index)?;
@ -624,7 +624,7 @@ impl Document {
Operation::SetNodeGraphFrameImageData { layer_path, image_data } => {
let layer = self.layer_mut(&layer_path).expect("Setting NodeGraphFrame image data for invalid layer");
if let LayerDataType::NodeGraphFrame(node_graph_frame) = &mut layer.data {
let image_data = std::rc::Rc::new(image_data);
let image_data = std::sync::Arc::new(image_data);
node_graph_frame.image_data = Some(crate::layers::nodegraph_layer::ImageData { image_data });
} else {
panic!("Incorrectly trying to set image data for a layer that is not an NodeGraphFrame layer type");
@ -850,7 +850,7 @@ impl Document {
Operation::ImaginateSetImageData { layer_path, image_data } => {
let layer = self.layer_mut(&layer_path).expect("Setting Imaginate image data for invalid layer");
if let LayerDataType::Imaginate(imaginate) = &mut layer.data {
let image_data = std::rc::Rc::new(image_data);
let image_data = std::sync::Arc::new(image_data);
imaginate.image_data = Some(ImaginateImageData { image_data });
} else {
panic!("Incorrectly trying to set image data for a layer that is not an Imaginate layer type");

View file

@ -2,14 +2,14 @@
use serde::{Deserialize, Deserializer, Serializer};
pub fn as_base64<S>(key: &std::rc::Rc<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
pub fn as_base64<S>(key: &std::sync::Arc<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&base64::encode(key.as_slice()))
}
pub fn from_base64<'a, D>(deserializer: D) -> Result<std::rc::Rc<Vec<u8>>, D::Error>
pub fn from_base64<'a, D>(deserializer: D) -> Result<std::sync::Arc<Vec<u8>>, D::Error>
where
D: Deserializer<'a>,
{
@ -17,6 +17,6 @@ where
String::deserialize(deserializer)
.and_then(|string| base64::decode(string).map_err(|err| Error::custom(err.to_string())))
.map(std::rc::Rc::new)
.map(std::sync::Arc::new)
.map_err(serde::de::Error::custom)
}

View file

@ -14,7 +14,7 @@ use std::fmt::Write;
pub struct ImageLayer {
pub mime: String,
#[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")]
pub image_data: std::rc::Rc<Vec<u8>>,
pub image_data: std::sync::Arc<Vec<u8>>,
// TODO: Have the browser dispose of this blob URL when this is dropped (like when the layer is deleted)
#[serde(skip)]
pub blob_url: Option<String>,
@ -75,7 +75,7 @@ impl LayerData for ImageLayer {
}
impl ImageLayer {
pub fn new(mime: String, image_data: std::rc::Rc<Vec<u8>>) -> Self {
pub fn new(mime: String, image_data: std::sync::Arc<Vec<u8>>) -> Self {
Self {
mime,
image_data,

View file

@ -57,7 +57,7 @@ pub enum ImaginateStatus {
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub struct ImaginateImageData {
#[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")]
pub image_data: std::rc::Rc<Vec<u8>>,
pub image_data: std::sync::Arc<Vec<u8>>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]

View file

@ -31,7 +31,7 @@ pub struct NodeGraphFrameLayer {
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub struct ImageData {
#[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")]
pub image_data: std::rc::Rc<Vec<u8>>,
pub image_data: std::sync::Arc<Vec<u8>>,
}
impl LayerData for NodeGraphFrameLayer {

View file

@ -11,4 +11,4 @@ homepage = "https://graphite.rs/libraries/bezier-rs"
repository = "https://github.com/GraphiteEditor/Graphite/libraries/bezier-rs"
[dependencies]
glam = { version = "0.17", features = ["serde"] }
glam = { version = "0.22", features = ["serde"] }

View file

@ -201,7 +201,7 @@ impl Bezier {
// Check if the bounding boxes overlap
if utils::do_rectangles_overlap(bounding_box1, bounding_box2) {
// If bounding boxes are within the error threshold (i.e. are small enough), we have found an intersection
if (bounding_box1[1] - bounding_box1[0]).lt(&error_threshold) && (bounding_box2[1] - bounding_box2[0]).lt(&error_threshold) {
if (bounding_box1[1] - bounding_box1[0]).cmplt(error_threshold).all() && (bounding_box2[1] - bounding_box2[0]).cmplt(error_threshold).all() {
// Use the middle t value, return the corresponding `t` value for `self` and `other`
return vec![[self_mid_t, other_mid_t]];
}

View file

@ -14,7 +14,7 @@ documentation = "https://docs.rs/dyn-any"
[dependencies]
dyn-any-derive = { path = "derive", version = "0.2.0", optional = true }
log = { version = "0.4", optional = true }
glam = { version = "0.17", optional = true }
glam = { version = "0.22", optional = true }
[features]
derive = ["dyn-any-derive"]
@ -23,6 +23,10 @@ log-bad-types = ["log"]
rc = []
# Opt into impls for some glam types
glam = ["dep:glam"]
alloc = []
large-atomics = []
std = ["alloc", "rc"]
default = ["std", "large-atomics"]
[package.metadata.docs.rs]
all-features = true

View file

@ -1,5 +1,9 @@
#![doc(html_root_url = "http://docs.rs/const-default/1.0.0")]
#![cfg_attr(feature = "unstable-docs", feature(doc_cfg))]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "derive")]
#[cfg_attr(feature = "unstable-docs", doc(cfg(feature = "derive")))]
@ -9,6 +13,7 @@ pub use dyn_any_derive::DynAny;
pub trait UpcastFrom<T: ?Sized> {
fn up_from(value: &T) -> &Self;
fn up_from_mut(value: &mut T) -> &mut Self;
#[cfg(feature = "alloc")]
fn up_from_box(value: Box<T>) -> Box<Self>;
}
@ -16,6 +21,7 @@ pub trait UpcastFrom<T: ?Sized> {
pub trait Upcast<U: ?Sized> {
fn up(&self) -> &U;
fn up_mut(&mut self) -> &mut U;
#[cfg(feature = "alloc")]
fn up_box(self: Box<Self>) -> Box<U>;
}
@ -29,12 +35,13 @@ where
fn up_mut(&mut self) -> &mut U {
U::up_from_mut(self)
}
#[cfg(feature = "alloc")]
fn up_box(self: Box<Self>) -> Box<U> {
U::up_from_box(self)
}
}
use std::any::TypeId;
use core::any::TypeId;
impl<'a, T: DynAny<'a> + 'a> UpcastFrom<T> for dyn DynAny<'a> + 'a {
fn up_from(value: &T) -> &(dyn DynAny<'a> + 'a) {
@ -43,6 +50,7 @@ impl<'a, T: DynAny<'a> + 'a> UpcastFrom<T> for dyn DynAny<'a> + 'a {
fn up_from_mut(value: &mut T) -> &mut (dyn DynAny<'a> + 'a) {
value
}
#[cfg(feature = "alloc")]
fn up_from_box(value: Box<T>) -> Box<Self> {
value
}
@ -55,16 +63,16 @@ pub trait DynAny<'a> {
}
impl<'a, T: StaticType> DynAny<'a> for T {
fn type_id(&self) -> std::any::TypeId {
std::any::TypeId::of::<T::Static>()
fn type_id(&self) -> core::any::TypeId {
core::any::TypeId::of::<T::Static>()
}
#[cfg(feature = "log-bad-types")]
fn type_name(&self) -> &'static str {
std::any::type_name::<T>()
core::any::type_name::<T>()
}
}
pub fn downcast_ref<'a, V: StaticType>(i: &'a dyn DynAny<'a>) -> Option<&'a V> {
if i.type_id() == std::any::TypeId::of::<<V as StaticType>::Static>() {
if i.type_id() == core::any::TypeId::of::<<V as StaticType>::Static>() {
// SAFETY: caller guarantees that T is the correct type
let ptr = i as *const dyn DynAny<'a> as *const V;
Some(unsafe { &*ptr })
@ -73,9 +81,10 @@ pub fn downcast_ref<'a, V: StaticType>(i: &'a dyn DynAny<'a>) -> Option<&'a V> {
}
}
#[cfg(feature = "alloc")]
pub fn downcast<'a, V: StaticType>(i: Box<dyn DynAny<'a> + 'a>) -> Option<Box<V>> {
let type_id = DynAny::type_id(i.as_ref());
if type_id == std::any::TypeId::of::<<V as StaticType>::Static>() {
if type_id == core::any::TypeId::of::<<V as StaticType>::Static>() {
// SAFETY: caller guarantees that T is the correct type
let ptr = Box::into_raw(i) as *mut dyn DynAny<'a> as *mut V;
Some(unsafe { Box::from_raw(ptr) })
@ -85,26 +94,24 @@ pub fn downcast<'a, V: StaticType>(i: Box<dyn DynAny<'a> + 'a>) -> Option<Box<V>
log::error!("Tried to downcast a {} to a {}", DynAny::type_name(i.as_ref()), core::any::type_name::<V>());
}
if type_id == std::any::TypeId::of::<&dyn DynAny<'static>>() {
panic!("downcast error: type_id == std::any::TypeId::of::<dyn DynAny<'a>>()");
if type_id == core::any::TypeId::of::<&dyn DynAny<'static>>() {
panic!("downcast error: type_id == core::any::TypeId::of::<dyn DynAny<'a>>()");
}
println!("expected: {:?}", std::any::TypeId::of::<<V as StaticType>::Static>());
println!("actual one: {:?}", type_id);
None
}
}
pub trait StaticType {
type Static: 'static + ?Sized;
fn type_id(&self) -> std::any::TypeId {
std::any::TypeId::of::<Self::Static>()
fn type_id(&self) -> core::any::TypeId {
core::any::TypeId::of::<Self::Static>()
}
}
pub trait StaticTypeSized {
type Static: 'static;
fn type_id(&self) -> std::any::TypeId {
std::any::TypeId::of::<Self::Static>()
fn type_id(&self) -> core::any::TypeId {
core::any::TypeId::of::<Self::Static>()
}
}
impl<T: StaticType + Sized> StaticTypeSized for T
@ -115,8 +122,8 @@ where
}
pub trait StaticTypeClone {
type Static: 'static + Clone;
fn type_id(&self) -> std::any::TypeId {
std::any::TypeId::of::<Self::Static>()
fn type_id(&self) -> core::any::TypeId {
core::any::TypeId::of::<Self::Static>()
}
}
impl<T: StaticType + Clone> StaticTypeClone for T
@ -135,8 +142,10 @@ macro_rules! impl_type {
)*
};
}
impl<'a, T: StaticTypeClone + Clone> StaticType for std::borrow::Cow<'a, T> {
type Static = std::borrow::Cow<'static, T::Static>;
#[cfg(feature = "alloc")]
impl<'a, T: StaticTypeClone + Clone> StaticType for Cow<'a, T> {
type Static = Cow<'static, T::Static>;
}
impl<T: StaticTypeSized> StaticType for *const [T] {
type Static = *const [<T as StaticTypeSized>::Static];
@ -163,19 +172,31 @@ impl<T: StaticTypeSized, const N: usize> StaticType for [T; N] {
impl<'a> StaticType for dyn DynAny<'a> + '_ {
type Static = dyn DynAny<'static>;
}
#[cfg(feature = "alloc")]
pub trait IntoDynAny<'n>: Sized + StaticType + 'n {
fn into_dyn(self) -> Box<dyn DynAny<'n> + 'n> {
Box::new(self)
}
}
#[cfg(feature = "alloc")]
impl<'n, T: StaticType + 'n> IntoDynAny<'n> for T {}
#[cfg(feature = "alloc")]
impl From<()> for Box<dyn DynAny<'static>> {
fn from(_: ()) -> Box<dyn DynAny<'static>> {
Box::new(())
}
}
#[cfg(feature = "alloc")]
use alloc::{
borrow::Cow,
boxed::Box,
collections::{BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque},
string::String,
vec::Vec,
};
use core::sync::atomic::*;
use core::{
cell::{Cell, RefCell, UnsafeCell},
iter::Empty,
@ -184,26 +205,38 @@ use core::{
num::Wrapping,
time::Duration,
};
use std::{
collections::*,
sync::{atomic::*, *},
vec::Vec,
};
impl_type!(
Option<T>, Result<T, E>, Cell<T>, UnsafeCell<T>, RefCell<T>, MaybeUninit<T>,
Vec<T>, String, BTreeMap<K,V>,BTreeSet<V>, LinkedList<T>, VecDeque<T>,
BinaryHeap<T>, ManuallyDrop<T>, PhantomData<T>, PhantomPinned, Empty<T>,
Wrapping<T>, Duration, Once, Mutex<T>, RwLock<T>, bool, f32, f64, char,
u8, AtomicU8, u16, AtomicU16, u32, AtomicU32, u64, AtomicU64, usize, AtomicUsize,
i8, AtomicI8, i16, AtomicI16, i32, AtomicI32, i64, AtomicI64, isize, AtomicIsize,
ManuallyDrop<T>, PhantomData<T>, PhantomPinned, Empty<T>,
Wrapping<T>, Duration, bool, f32, f64, char,
u8, AtomicU8, u16, AtomicU16, u32, AtomicU32, u64, usize, AtomicUsize,
i8, AtomicI8, i16, AtomicI16, i32, AtomicI32, i64, isize, AtomicIsize,
i128, u128, AtomicBool, AtomicPtr<T>
);
#[cfg(feature = "large-atomics")]
impl_type!(AtomicU64, AtomicI64);
#[cfg(feature = "alloc")]
impl_type!(
Vec<T>, String, BTreeMap<K,V>,BTreeSet<V>, LinkedList<T>, VecDeque<T>,
BinaryHeap<T>
);
#[cfg(feature = "std")]
use std::sync::*;
#[cfg(feature = "std")]
impl_type!(Once, Mutex<T>, RwLock<T>);
#[cfg(feature = "rc")]
use std::{rc::Rc, sync::Arc};
use std::rc::Rc;
#[cfg(feature = "rc")]
impl_type!(Rc<T>, Arc<T>);
impl_type!(Rc<T>);
#[cfg(all(feature = "rc", feature = "alloc"))]
use std::sync::Arc;
#[cfg(all(feature = "rc", feature = "alloc"))]
impl_type!(Arc<T>);
#[cfg(feature = "glam")]
use glam::*;
@ -216,6 +249,7 @@ impl_type!(
Quat, Affine2, Affine3A, DAffine2, DAffine3, DQuat
);
#[cfg(feature = "alloc")]
impl<T: crate::StaticType + ?Sized> crate::StaticType for Box<T> {
type Static = Box<<T as crate::StaticType>::Static>;
}

View file

@ -9,17 +9,26 @@ license = "MIT OR Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
std = ["dyn-any"]
default = ["async", "serde"]
gpu = ["spirv-std", "bytemuck"]
async = ["async-trait"]
std = ["dyn-any", "dyn-any/std"]
default = ["async", "serde", "kurbo", "log"]
log = ["dep:log"]
serde = ["dep:serde", "glam/serde"]
gpu = ["spirv-std", "bytemuck", "glam/bytemuck", "dyn-any"]
async = ["async-trait", "alloc"]
nightly = []
serde = ["dep:serde"]
alloc = ["dyn-any", "bezier-rs"]
[dependencies]
dyn-any = {path = "../../libraries/dyn-any", features = ["derive"], optional = true}
dyn-any = {path = "../../libraries/dyn-any", features = ["derive"], optional = true, default-features = false }
spirv-std = { git = "https://github.com/EmbarkStudios/rust-gpu", features = ["glam"] , optional = true}
bytemuck = {version = "1.8", features = ["derive"], optional = true}
async-trait = {version = "0.1", optional = true}
serde = {version = "1.0", features = ["derive"], optional = true}
serde = {version = "1.0", features = ["derive"], optional = true, default-features = false }
log = {version = "0.4", optional = true}
bezier-rs = { path = "../../libraries/bezier-rs", optional = true }
kurbo = { git = "https://github.com/linebender/kurbo.git", features = [
"serde",
], optional = true }
glam = { version = "^0.22", default-features = false, features = ["scalar-math", "libm"]}

View file

@ -1,7 +1,12 @@
#![no_std]
#[cfg(feature = "async")]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg_attr(feature = "log", macro_use)]
#[cfg(feature = "log")]
extern crate log;
#[cfg(feature = "async")]
use alloc::boxed::Box;
#[cfg(feature = "async")]
@ -17,6 +22,9 @@ pub mod gpu;
pub mod raster;
#[cfg(feature = "alloc")]
pub mod vector;
pub trait Node<T> {
type Output;

View file

@ -1,7 +1,7 @@
use crate::Node;
pub mod color;
use self::color::Color;
pub use self::color::Color;
#[derive(Debug, Clone, Copy)]
pub struct GrayscaleColorNode;
@ -21,6 +21,12 @@ impl<'n> Node<Color> for &'n GrayscaleColorNode {
}
}
impl GrayscaleColorNode {
pub fn new() -> Self {
Self
}
}
#[derive(Debug, Clone, Copy)]
pub struct BrightenColorNode<N: Node<(), Output = f32>>(N);
@ -48,8 +54,10 @@ impl<N: Node<(), Output = f32> + Copy> BrightenColorNode<N> {
}
#[derive(Debug, Clone, Copy)]
#[cfg(not(target_arch = "spirv"))]
pub struct HueShiftColorNode<N: Node<(), Output = f32>>(N);
#[cfg(not(target_arch = "spirv"))]
impl<N: Node<(), Output = f32>> Node<Color> for HueShiftColorNode<N> {
type Output = Color;
fn eval(self, color: Color) -> Color {
@ -58,6 +66,7 @@ impl<N: Node<(), Output = f32>> Node<Color> for HueShiftColorNode<N> {
Color::from_hsla(hue + hue_shift / 360., saturation, lightness, alpha)
}
}
#[cfg(not(target_arch = "spirv"))]
impl<N: Node<(), Output = f32> + Copy> Node<Color> for &HueShiftColorNode<N> {
type Output = Color;
fn eval(self, color: Color) -> Color {
@ -67,6 +76,7 @@ impl<N: Node<(), Output = f32> + Copy> Node<Color> for &HueShiftColorNode<N> {
}
}
#[cfg(not(target_arch = "spirv"))]
impl<N: Node<(), Output = f32> + Copy> HueShiftColorNode<N> {
pub fn new(node: N) -> Self {
Self(node)
@ -85,6 +95,48 @@ where
}
}
#[cfg(feature = "alloc")]
pub use image::Image;
#[cfg(feature = "alloc")]
mod image {
use super::Color;
use alloc::vec::Vec;
use dyn_any::{DynAny, StaticType};
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Image {
pub width: u32,
pub height: u32,
pub data: Vec<Color>,
}
impl Image {
pub const fn empty() -> Self {
Self {
width: 0,
height: 0,
data: Vec::new(),
}
}
}
impl IntoIterator for Image {
type Item = Color;
type IntoIter = alloc::vec::IntoIter<Color>;
fn into_iter(self) -> Self::IntoIter {
self.data.into_iter()
}
}
impl<'a> IntoIterator for &'a Image {
type Item = &'a Color;
type IntoIter = alloc::slice::Iter<'a, Color>;
fn into_iter(self) -> Self::IntoIter {
self.data.iter()
}
}
}
/*pub struct MutWrapper<N>(pub N);
impl<'n, T: Clone, N> Node<&'n mut T> for &'n MutWrapper<N>

View file

@ -3,13 +3,23 @@ use dyn_any::{DynAny, StaticType};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(target_arch = "spirv")]
use spirv_std::num_traits::float::Float;
#[cfg(target_arch = "spirv")]
use spirv_std::num_traits::Euclid;
#[cfg(feature = "gpu")]
use bytemuck::{Pod, Zeroable};
/// Structure that represents a color.
/// Internally alpha is stored as `f32` that ranges from `0.0` (transparent) to `1.0` (opaque).
/// The other components (RGB) are stored as `f32` that range from `0.0` up to `f32::MAX`,
/// the values encode the brightness of each channel proportional to the light intensity in cd/m² (nits) in HDR, and `0.0` (black) to `1.0` (white) in SDR color.
#[repr(C)]
#[cfg_attr(feature = "std", derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize, DynAny))]
#[cfg_attr(not(feature = "std"), derive(Debug, Clone, Copy, PartialEq, Default))]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, DynAny))]
#[cfg_attr(feature = "gpu", derive(Pod, Zeroable))]
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Color {
red: f32,
green: f32,
@ -92,6 +102,7 @@ impl Color {
/// use graphene_core::raster::color::Color;
/// let color = Color::from_hsla(0.5, 0.2, 0.3, 1.);
/// ```
#[cfg(not(target_arch = "spirv"))]
pub fn from_hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Color {
let temp1 = if lightness < 0.5 {
lightness * (saturation + 1.)
@ -219,7 +230,10 @@ impl Color {
} else {
4. + (self.red - self.green) / (max_channel - min_channel)
} / 6.;
#[cfg(not(target_arch = "spirv"))]
let hue = hue.rem_euclid(1.);
#[cfg(target_arch = "spirv")]
let hue = hue.rem_euclid(&1.);
[hue, saturation, lightness, self.alpha]
}

View file

@ -1,4 +1,4 @@
use std::ops::{Index, IndexMut};
use core::ops::{Index, IndexMut};
use serde::{Deserialize, Serialize};

View file

@ -1,7 +1,5 @@
use std::sync::Arc;
use crate::Node;
use glam::{DAffine2, DVec2};
use graphene_core::Node;
use super::subpath::Subpath;
@ -40,8 +38,10 @@ impl Node<()> for &UnitSquareGenerator {
}
}
// TODO: I removed the Arc requirement we shouuld think about when it makes sense to use its
// vs making a generic value node
#[derive(Debug, Clone)]
pub struct PathGenerator(Arc<Subpath>);
pub struct PathGenerator(Subpath);
impl Node<()> for PathGenerator {
type Output = VectorData;
@ -53,7 +53,7 @@ impl Node<()> for PathGenerator {
impl Node<()> for &PathGenerator {
type Output = VectorData;
fn eval(self, _input: ()) -> Self::Output {
(*self.0).clone()
(self.0).clone()
}
}
use crate::raster::Image;
@ -65,7 +65,7 @@ impl<N: Node<(), Output = Subpath>> Node<Image> for BlitSubpath<N> {
type Output = Image;
fn eval(self, input: Image) -> Self::Output {
let subpath = self.0.eval(());
info!("Blitting subpath {subpath:?}");
log::info!("Blitting subpath {subpath:?}");
input
}
}
@ -74,7 +74,7 @@ impl<N: Node<(), Output = Subpath> + Copy> Node<Image> for &BlitSubpath<N> {
type Output = Image;
fn eval(self, input: Image) -> Self::Output {
let subpath = self.0.eval(());
info!("Blitting subpath {subpath:?}");
log::info!("Blitting subpath {subpath:?}");
input
}
}

View file

@ -1,5 +1,8 @@
use core::ops::{Deref, DerefMut};
use serde::{Deserialize, Serialize};
use std::ops::{Deref, DerefMut};
use alloc::vec;
use alloc::vec::Vec;
/// Brief description: A vec that allows indexing elements by both index and an assigned unique ID
/// Goals of this Data Structure:
@ -121,7 +124,7 @@ impl<T> IdBackedVec<T> {
}
/// Enumerate the ids and elements in this container `(&ElementId, &T)`
pub fn enumerate(&self) -> std::iter::Zip<core::slice::Iter<u64>, core::slice::Iter<T>> {
pub fn enumerate(&self) -> core::iter::Zip<core::slice::Iter<u64>, core::slice::Iter<T>> {
self.element_ids.iter().zip(self.elements.iter())
}

View file

@ -3,6 +3,9 @@ use super::id_vec::IdBackedVec;
use super::manipulator_group::ManipulatorGroup;
use super::manipulator_point::ManipulatorPoint;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use dyn_any::{DynAny, StaticType};
use glam::{DAffine2, DVec2};
use kurbo::{BezPath, PathEl, Shape};
@ -92,7 +95,7 @@ impl Subpath {
pub fn new_ngon(center: DVec2, sides: u64, radius: f64) -> Self {
let mut manipulator_groups = vec![];
for i in 0..sides {
let angle = (i as f64) * std::f64::consts::TAU / (sides as f64);
let angle = (i as f64) * core::f64::consts::TAU / (sides as f64);
let center = center + DVec2::ONE * radius;
let position = ManipulatorGroup::new_with_anchor(DVec2::new(center.x + radius * f64::cos(angle), center.y + radius * f64::sin(angle)) * 0.5);
@ -346,7 +349,7 @@ impl Subpath {
/// Generate an SVG `path` elements's `d` attribute: `<path d="...">`.
pub fn to_svg(&mut self) -> String {
fn write_positions(result: &mut String, values: [Option<DVec2>; 3]) {
use std::fmt::Write;
use core::fmt::Write;
let count = values.into_iter().flatten().count();
for (index, pos) in values.into_iter().flatten().enumerate() {
write!(result, "{},{}", pos.x, pos.y).unwrap();
@ -442,7 +445,7 @@ impl BezierId {
/// An iterator over [`bezier_rs::Bezier`] segments constructable via [`Subpath::bezier_iter`].
pub struct PathIter<'a> {
path: std::iter::Zip<core::slice::Iter<'a, u64>, core::slice::Iter<'a, ManipulatorGroup>>,
path: core::iter::Zip<core::slice::Iter<'a, u64>, core::slice::Iter<'a, ManipulatorGroup>>,
last_anchor: Option<DVec2>,
last_out_handle: Option<DVec2>,

View file

@ -8,13 +8,12 @@ license = "MIT OR Apache-2.0"
default = []
profiling = ["nvtx", "gpu"]
gpu = ["serde", "vulkano", "spirv-builder", "tera", "graphene-core/gpu"]
serde = ["dep:serde", "graphene-std/serde", "glam/serde"]
serde = ["dep:serde", "graphene-core/serde", "glam/serde"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
graphene-core = { path = "../gcore", features = ["async", "std" ] }
graphene-std = { path = "../gstd" }
graphene-core = { path = "../gcore", features = ["async", "std", "alloc"] }
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types", "rc", "glam"] }
num-traits = "0.2"
borrow_stack = { path = "../borrow_stack" }
@ -22,7 +21,7 @@ dyn-clone = "1.0"
rand_chacha = "0.3.1"
log = "0.4"
serde = { version = "1", features = ["derive", "rc"], optional = true }
glam = { version = "0.17" }
glam = { version = "0.22" }
vulkano = {git = "https://github.com/GraphiteEditor/vulkano", branch = "fix_rust_gpu", optional = true}
bytemuck = {version = "1.8" }

View file

@ -5,6 +5,7 @@ use std::sync::Mutex;
pub mod value;
use dyn_any::{DynAny, StaticType};
use rand_chacha::{
rand_core::{RngCore, SeedableRng},
ChaCha20Rng,
@ -138,7 +139,7 @@ pub enum DocumentNodeImplementation {
Unresolved(NodeIdentifier),
}
#[derive(Clone, Debug, Default, PartialEq)]
#[derive(Clone, Debug, Default, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NodeNetwork {
pub inputs: Vec<NodeId>,

View file

@ -15,10 +15,10 @@ pub enum TaggedValue {
F64(f64),
Bool(bool),
DVec2(DVec2),
Image(graphene_std::raster::Image),
Image(graphene_core::raster::Image),
Color(graphene_core::raster::color::Color),
Subpath(graphene_std::vector::subpath::Subpath),
RcSubpath(Arc<graphene_std::vector::subpath::Subpath>),
Subpath(graphene_core::vector::subpath::Subpath),
RcSubpath(Arc<graphene_core::vector::subpath::Subpath>),
}
impl TaggedValue {

View file

@ -1,10 +1,8 @@
use std::error::Error;
use borrow_stack::{BorrowStack, FixedSizeStack};
use graphene_core::Node;
use graphene_std::any::{Any, TypeErasedNode};
use dyn_any::DynAny;
use crate::{document::NodeNetwork, node_registry::push_node, proto::ProtoNetwork};
use crate::{document::NodeNetwork, proto::ProtoNetwork};
pub struct Compiler {}
@ -25,30 +23,8 @@ impl Compiler {
proto_network
}
}
pub type Any<'a> = Box<dyn DynAny<'a> + 'a>;
pub trait Executor {
fn execute(&self, input: Any<'static>) -> Result<Any<'static>, Box<dyn Error>>;
}
pub struct DynamicExecutor {
stack: FixedSizeStack<TypeErasedNode<'static>>,
}
impl DynamicExecutor {
pub fn new(proto_network: ProtoNetwork) -> Self {
assert_eq!(proto_network.inputs.len(), 1);
let node_count = proto_network.nodes.len();
let stack = FixedSizeStack::new(node_count);
for (_id, node) in proto_network.nodes {
push_node(node, &stack);
}
Self { stack }
}
}
impl Executor for DynamicExecutor {
fn execute(&self, input: Any<'static>) -> Result<Any<'static>, Box<dyn Error>> {
let result = unsafe { self.stack.get().last().unwrap().eval(input) };
Ok(result)
}
}

View file

@ -30,6 +30,10 @@ pub fn create_files(matadata: &Metadata, network: &ProtoNetwork, compile_dir: &P
let cargo_toml = create_cargo_toml(matadata)?;
std::fs::write(cargo_file, cargo_toml)?;
let toolchain_file = compile_dir.join("rust-toolchain");
let toolchain = include_str!("templates/rust-toolchain");
std::fs::write(toolchain_file, toolchain)?;
// create src dir
match std::fs::create_dir(&src) {
Ok(_) => {}

View file

@ -1,7 +1,7 @@
use std::path::Path;
use super::{compiler::Metadata, context::Context};
use crate::gpu::compiler;
use crate::{executor::Any, gpu::compiler};
use bytemuck::Pod;
use dyn_any::StaticTypeSized;
use vulkano::{
@ -44,7 +44,7 @@ impl<I: StaticTypeSized, O> GpuExecutor<I, O> {
}
impl<I: StaticTypeSized + Sync + Pod + Send, O: StaticTypeSized + Send + Sync + Pod> crate::executor::Executor for GpuExecutor<I, O> {
fn execute(&self, input: graphene_std::any::Any<'static>) -> Result<graphene_std::any::Any<'static>, Box<dyn std::error::Error>> {
fn execute(&self, input: Any<'static>) -> Result<Any<'static>, Box<dyn std::error::Error>> {
let input = dyn_any::downcast::<Vec<I>>(input).expect("Wrong input type");
let context = &self.context;
let result: Vec<O> = execute_shader(
@ -91,7 +91,7 @@ fn execute_shader<I: Pod + Send + Sync, O: Pod + Send + Sync>(
.bind_pipeline_compute(compute_pipeline.clone())
.bind_descriptor_sets(PipelineBindPoint::Compute, compute_pipeline.layout().clone(), 0, set)
.push_constants(compute_pipeline.layout().clone(), 0, constants)
.dispatch([1024, 1, 1])
.dispatch([((constants.n as isize - 1) / 1024 + 1) as u32 * 1024, 1, 1])
.unwrap();
let command_buffer = builder.build().unwrap();

View file

@ -1,8 +1,6 @@
#[macro_use]
extern crate log;
pub mod node_registry;
pub mod document;
pub mod proto;
@ -10,124 +8,3 @@ pub mod executor;
#[cfg(feature = "gpu")]
pub mod gpu;
#[cfg(test)]
mod tests {
use std::marker::PhantomData;
use graphene_core::value::ValueNode;
use graphene_core::{structural::*, RefNode};
use borrow_stack::BorrowStack;
use dyn_any::{downcast, IntoDynAny};
use graphene_std::any::{Any, DowncastNode, DynAnyNode, TypeErasedNode};
use graphene_std::ops::AddNode;
#[test]
fn borrow_stack() {
let stack = borrow_stack::FixedSizeStack::new(256);
unsafe {
let dynanynode: DynAnyNode<ValueNode<u32>, (), _, _> = DynAnyNode::new(ValueNode(2_u32));
stack.push(dynanynode.into_box());
}
stack.push_fn(|nodes| {
let pre_node = nodes.get(0).unwrap();
let downcast: DowncastNode<&TypeErasedNode, &u32> = DowncastNode::new(pre_node);
let dynanynode: DynAnyNode<ConsNode<_, Any<'_>>, u32, _, _> = DynAnyNode::new(ConsNode(downcast, PhantomData));
dynanynode.into_box()
});
stack.push_fn(|_| {
let dynanynode: DynAnyNode<_, (u32, &u32), _, _> = DynAnyNode::new(AddNode);
dynanynode.into_box()
});
stack.push_fn(|nodes| {
let compose_node = nodes[1].after(&nodes[2]);
TypeErasedNode(Box::new(compose_node))
});
let result = unsafe { &stack.get()[0] }.eval_ref(().into_dyn());
assert_eq!(*downcast::<&u32>(result).unwrap(), &2_u32);
let result = unsafe { &stack.get()[1] }.eval_ref(4_u32.into_dyn());
assert_eq!(*downcast::<(u32, &u32)>(result).unwrap(), (4_u32, &2_u32));
let result = unsafe { &stack.get()[1] }.eval_ref(4_u32.into_dyn());
let add = unsafe { &stack.get()[2] }.eval_ref(result);
assert_eq!(*downcast::<u32>(add).unwrap(), 6_u32);
let add = unsafe { &stack.get()[3] }.eval_ref(4_u32.into_dyn());
assert_eq!(*downcast::<u32>(add).unwrap(), 6_u32);
}
#[test]
fn execute_add() {
use crate::document::*;
use crate::proto::*;
fn add_network() -> NodeNetwork {
NodeNetwork {
inputs: vec![0, 0],
output: 1,
nodes: [
(
0,
DocumentNode {
name: "Cons".into(),
inputs: vec![NodeInput::Network, NodeInput::Network],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
"graphene_core::structural::ConsNode",
&[Type::Concrete(std::borrow::Cow::Borrowed("u32")), Type::Concrete(std::borrow::Cow::Borrowed("u32"))],
)),
metadata: DocumentNodeMetadata::default(),
},
),
(
1,
DocumentNode {
name: "Add".into(),
inputs: vec![NodeInput::Node(0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
"graphene_core::ops::AddNode",
&[Type::Concrete(std::borrow::Cow::Borrowed("u32")), Type::Concrete(std::borrow::Cow::Borrowed("u32"))],
)),
metadata: DocumentNodeMetadata::default(),
},
),
]
.into_iter()
.collect(),
}
}
let network = NodeNetwork {
inputs: vec![0],
output: 0,
nodes: [(
0,
DocumentNode {
name: "Inc".into(),
inputs: vec![
NodeInput::Network,
NodeInput::Value {
tagged_value: value::TaggedValue::U32(1),
exposed: false,
},
],
implementation: DocumentNodeImplementation::Network(add_network()),
metadata: DocumentNodeMetadata::default(),
},
)]
.into_iter()
.collect(),
};
use crate::executor::{Compiler, DynamicExecutor, Executor};
let compiler = Compiler {};
let protograph = compiler.compile(network, false);
let exec = DynamicExecutor::new(protograph);
let result = exec.execute(32_u32.into_dyn()).unwrap();
let val = *dyn_any::downcast::<u32>(result).unwrap();
assert_eq!(val, 33_u32);
}
}

View file

@ -321,17 +321,12 @@ impl ProtoNetwork {
}
fn replace_node_references(&mut self, lookup: &HashMap<u64, u64>) {
self.nodes.iter_mut().for_each(|(sid, node)| {
self.nodes.iter_mut().for_each(|(_, node)| {
node.map_ids(|id| *lookup.get(&id).expect("node not found in lookup table"));
});
self.inputs = self.inputs.iter().map(|id| *lookup.get(id).unwrap()).collect();
self.output = *lookup.get(&self.output).unwrap();
}
fn replace_ids_with_lookup(&mut self, lookup: HashMap<u64, u64>) {
self.nodes.iter_mut().for_each(|(id, _)| *id = *lookup.get(id).unwrap());
self.replace_node_references(&lookup);
}
}
#[cfg(test)]

View file

@ -12,13 +12,17 @@ license = "MIT OR Apache-2.0"
derive = ["graph-proc-macros"]
memoization = ["once_cell"]
default = ["derive", "memoization"]
gpu = ["graph-craft/gpu", "graphene-core/gpu"]
[dependencies]
graphene-core = {path = "../gcore", features = ["async", "std"], default-features = false}
graphene-core = {path = "../gcore", features = ["async", "std" ], default-features = false}
borrow_stack = {path = "../borrow_stack"}
dyn-any = {path = "../../libraries/dyn-any", features = ["derive"]}
graph-proc-macros = {path = "../proc-macro", optional = true}
graph-craft = {path = "../graph-craft"}
bytemuck = {version = "1.8" }
tempfile = "3"
once_cell = {version= "1.10", optional = true}
#pretty-token-stream = {path = "../../pretty-token-stream"}
syn = {version = "1.0", default-features = false, features = ["parsing", "printing"]}
@ -32,7 +36,7 @@ bezier-rs = { path = "../../libraries/bezier-rs" }
kurbo = { git = "https://github.com/linebender/kurbo.git", features = [
"serde",
] }
glam = { version = "0.17", features = ["serde"] }
glam = { version = "0.22", features = ["serde"] }
[dependencies.serde]
version = "1.0"

View file

@ -30,7 +30,9 @@ where
input.borrow().hash(&mut hasher);
let hash = hasher.finish();
self.map.get_or_create_with(&hash, || CacheNode::new(self.node))
self.map.get_or_create_with(&hash, ||{
trace!("Creating new cache node");
CacheNode::new(self.node)})
}
}

Some files were not shown because too many files have changed in this diff Show more