mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 23:58:11 +08:00
Merge remote-tracking branch 'origin/master' into spiral-node
This commit is contained in:
@@ -4,7 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
pull_request:
|
pull_request: {}
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
INDEX_HTML_HEAD_REPLACEMENT: <script defer data-domain="dev.graphite.rs" data-api="https://graphite.rs/visit/event" src="https://graphite.rs/visit/script.hash.js"></script>
|
INDEX_HTML_HEAD_REPLACEMENT: <script defer data-domain="dev.graphite.rs" data-api="https://graphite.rs/visit/event" src="https://graphite.rs/visit/script.hash.js"></script>
|
||||||
@@ -13,9 +13,10 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: write
|
||||||
deployments: write
|
deployments: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
actions: write
|
||||||
env:
|
env:
|
||||||
RUSTC_WRAPPER: /usr/bin/sccache
|
RUSTC_WRAPPER: /usr/bin/sccache
|
||||||
CARGO_INCREMENTAL: 0
|
CARGO_INCREMENTAL: 0
|
||||||
@@ -47,9 +48,11 @@ jobs:
|
|||||||
rustc --version
|
rustc --version
|
||||||
|
|
||||||
- name: ✂ Replace template in <head> of index.html
|
- name: ✂ Replace template in <head> of index.html
|
||||||
|
if: github.ref != 'refs/heads/master'
|
||||||
|
env:
|
||||||
|
INDEX_HTML_HEAD_REPLACEMENT: ""
|
||||||
run: |
|
run: |
|
||||||
# Remove the INDEX_HTML_HEAD_REPLACEMENT environment variable for build links (not master deploys)
|
# Remove the INDEX_HTML_HEAD_REPLACEMENT environment variable for build links (not master deploys)
|
||||||
git rev-parse --abbrev-ref HEAD | grep master > /dev/null || export INDEX_HTML_HEAD_REPLACEMENT=""
|
|
||||||
sed -i "s|<!-- INDEX_HTML_HEAD_REPLACEMENT -->|$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html
|
sed -i "s|<!-- INDEX_HTML_HEAD_REPLACEMENT -->|$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html
|
||||||
|
|
||||||
- name: 🌐 Build Graphite web code
|
- name: 🌐 Build Graphite web code
|
||||||
@@ -70,6 +73,19 @@ jobs:
|
|||||||
projectName: graphite-dev
|
projectName: graphite-dev
|
||||||
directory: frontend/dist
|
directory: frontend/dist
|
||||||
|
|
||||||
|
- name: 💬 Comment build link URL to commit hash page on GitHub
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
gh api \
|
||||||
|
-X POST \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
/repos/${{ github.repository }}/commits/$(git rev-parse HEAD)/comments \
|
||||||
|
-f body="| 📦 **Build Complete for** $(git rev-parse HEAD) |
|
||||||
|
|-|
|
||||||
|
| ${{ steps.cloudflare.outputs.url }} |"
|
||||||
|
|
||||||
- name: 👕 Lint Graphite web formatting
|
- name: 👕 Lint Graphite web formatting
|
||||||
env:
|
env:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
@@ -91,6 +107,51 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mold -run cargo test --all-features --workspace
|
mold -run cargo test --all-features --workspace
|
||||||
|
|
||||||
|
- name: 📃 Generate code documentation info for website
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
run: |
|
||||||
|
cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree
|
||||||
|
mkdir -p artifacts-generated
|
||||||
|
mv hierarchical_message_system_tree.txt artifacts-generated/hierarchical_message_system_tree.txt
|
||||||
|
|
||||||
|
- name: 💿 Obtain cache of auto-generated code docs artifacts, to check if they've changed
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
id: cache-website-code-docs
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
key: website-code-docs
|
||||||
|
|
||||||
|
- name: 🔍 Check if auto-generated code docs artifacts changed
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
id: website-code-docs-changed
|
||||||
|
run: |
|
||||||
|
if ! diff --brief --recursive artifacts-generated artifacts; then
|
||||||
|
echo "Auto-generated code docs artifacts have changed."
|
||||||
|
rm -rf artifacts
|
||||||
|
mv artifacts-generated artifacts
|
||||||
|
echo "changed=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "Auto-generated code docs artifacts have not changed."
|
||||||
|
rm -rf artifacts
|
||||||
|
rm -rf artifacts-generated
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: 💾 Save cache of auto-generated code docs artifacts
|
||||||
|
if: steps.website-code-docs-changed.outputs.changed == 'true'
|
||||||
|
uses: actions/cache/save@v3
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
key: ${{ steps.cache-website-code-docs.outputs.cache-primary-key }}
|
||||||
|
|
||||||
|
- name: ♻️ Trigger website rebuild if the auto-generated code docs artifacts have changed
|
||||||
|
if: steps.website-code-docs-changed.outputs.changed == 'true'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
rm -rf artifacts
|
||||||
|
gh workflow run website.yml --ref master
|
||||||
|
|
||||||
# miri:
|
# miri:
|
||||||
# runs-on: self-hosted
|
# runs-on: self-hosted
|
||||||
|
|
||||||
|
|||||||
@@ -73,9 +73,10 @@ jobs:
|
|||||||
rustc --version
|
rustc --version
|
||||||
|
|
||||||
- name: ✂ Replace template in <head> of index.html
|
- name: ✂ Replace template in <head> of index.html
|
||||||
|
env:
|
||||||
|
INDEX_HTML_HEAD_REPLACEMENT: ""
|
||||||
run: |
|
run: |
|
||||||
# Remove the INDEX_HTML_HEAD_REPLACEMENT environment variable for build links (not master deploys)
|
# Remove the INDEX_HTML_HEAD_REPLACEMENT environment variable for build links (not master deploys)
|
||||||
export INDEX_HTML_HEAD_REPLACEMENT=""
|
|
||||||
sed -i "s|<!-- INDEX_HTML_HEAD_REPLACEMENT -->|$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html
|
sed -i "s|<!-- INDEX_HTML_HEAD_REPLACEMENT -->|$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html
|
||||||
|
|
||||||
- name: ⌨ Set build command based on comment
|
- name: ⌨ Set build command based on comment
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- website/**
|
- website/**
|
||||||
|
workflow_dispatch: {}
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
INDEX_HTML_HEAD_INCLUSION: <script defer data-domain="graphite.rs" data-api="/visit/event" src="/visit/script.hash.js"></script>
|
INDEX_HTML_HEAD_INCLUSION: <script defer data-domain="graphite.rs" data-api="/visit/event" src="/visit/script.hash.js"></script>
|
||||||
@@ -30,6 +31,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
tool: zola@0.20.0
|
tool: zola@0.20.0
|
||||||
|
|
||||||
|
- name: 🔍 Check if `website/other` directory changed
|
||||||
|
uses: dorny/paths-filter@v3
|
||||||
|
id: changes
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
website-other:
|
||||||
|
- "website/other/**"
|
||||||
|
|
||||||
- name: ✂ Replace template in <head> of index.html
|
- name: ✂ Replace template in <head> of index.html
|
||||||
run: |
|
run: |
|
||||||
# Remove the INDEX_HTML_HEAD_INCLUSION environment variable for build links (not master deploys)
|
# Remove the INDEX_HTML_HEAD_INCLUSION environment variable for build links (not master deploys)
|
||||||
@@ -43,16 +52,8 @@ jobs:
|
|||||||
npm run install-fonts
|
npm run install-fonts
|
||||||
zola --config config.toml build --minify
|
zola --config config.toml build --minify
|
||||||
|
|
||||||
- name: 🔍 Check if `website/other` directory changed
|
|
||||||
uses: dorny/paths-filter@v3
|
|
||||||
id: changes
|
|
||||||
with:
|
|
||||||
filters: |
|
|
||||||
other:
|
|
||||||
- "website/other/**"
|
|
||||||
|
|
||||||
- name: 💿 Restore cache of `website/other/dist` directory, if available and `website/other` didn't change
|
- name: 💿 Restore cache of `website/other/dist` directory, if available and `website/other` didn't change
|
||||||
if: steps.changes.outputs.other != 'true'
|
if: steps.changes.outputs.website-other != 'true'
|
||||||
id: cache-website-other-dist
|
id: cache-website-other-dist
|
||||||
uses: actions/cache/restore@v3
|
uses: actions/cache/restore@v3
|
||||||
with:
|
with:
|
||||||
@@ -80,8 +81,32 @@ jobs:
|
|||||||
|
|
||||||
- name: 🚚 Move `website/other/dist` contents to `website/public`
|
- name: 🚚 Move `website/other/dist` contents to `website/public`
|
||||||
run: |
|
run: |
|
||||||
|
mkdir -p website/public
|
||||||
mv website/other/dist/* website/public
|
mv website/other/dist/* website/public
|
||||||
|
|
||||||
|
- name: 💿 Obtain cache of auto-generated code docs artifacts
|
||||||
|
id: cache-website-code-docs
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
key: website-code-docs
|
||||||
|
|
||||||
|
- name: 📁 Fallback in case auto-generated code docs artifacts weren't cached
|
||||||
|
if: steps.cache-website-code-docs.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
echo "🦀 Initial system version of Rust:"
|
||||||
|
rustc --version
|
||||||
|
rustup update stable
|
||||||
|
echo "🦀 Latest updated version of Rust:"
|
||||||
|
rustc --version
|
||||||
|
cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree
|
||||||
|
mkdir artifacts
|
||||||
|
mv hierarchical_message_system_tree.txt artifacts/hierarchical_message_system_tree.txt
|
||||||
|
|
||||||
|
- name: 🚚 Move `artifacts` contents to `website/public`
|
||||||
|
run: |
|
||||||
|
mv artifacts/* website/public
|
||||||
|
|
||||||
- name: 📤 Publish to Cloudflare Pages
|
- name: 📤 Publish to Cloudflare Pages
|
||||||
id: cloudflare
|
id: cloudflare
|
||||||
uses: cloudflare/pages-action@1
|
uses: cloudflare/pages-action@1
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ profile.json
|
|||||||
flamegraph.svg
|
flamegraph.svg
|
||||||
.idea/
|
.idea/
|
||||||
.direnv
|
.direnv
|
||||||
|
hierarchical_message_system_tree.txt
|
||||||
|
|||||||
Generated
+982
-769
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ accepted = [
|
|||||||
"BSD-3-Clause",
|
"BSD-3-Clause",
|
||||||
"BSL-1.0",
|
"BSL-1.0",
|
||||||
"CC0-1.0",
|
"CC0-1.0",
|
||||||
|
"CDLA-Permissive-2.0",
|
||||||
"ISC",
|
"ISC",
|
||||||
"MIT-0",
|
"MIT-0",
|
||||||
"MIT",
|
"MIT",
|
||||||
@@ -14,6 +15,7 @@ accepted = [
|
|||||||
"Unicode-3.0",
|
"Unicode-3.0",
|
||||||
"Unicode-DFS-2016",
|
"Unicode-DFS-2016",
|
||||||
"Zlib",
|
"Zlib",
|
||||||
|
"NCSA",
|
||||||
]
|
]
|
||||||
workarounds = ["ring"]
|
workarounds = ["ring"]
|
||||||
ignore-build-dependencies = true
|
ignore-build-dependencies = true
|
||||||
|
|||||||
@@ -75,11 +75,14 @@ allow = [
|
|||||||
"BSD-3-Clause",
|
"BSD-3-Clause",
|
||||||
"BSL-1.0",
|
"BSL-1.0",
|
||||||
"CC0-1.0",
|
"CC0-1.0",
|
||||||
|
"CDLA-Permissive-2.0",
|
||||||
"ISC",
|
"ISC",
|
||||||
|
"MIT-0",
|
||||||
"MIT",
|
"MIT",
|
||||||
"MPL-2.0",
|
"MPL-2.0",
|
||||||
"OpenSSL",
|
"OpenSSL",
|
||||||
"Unicode-3.0",
|
"Unicode-3.0",
|
||||||
|
"Unicode-DFS-2016",
|
||||||
"Zlib",
|
"Zlib",
|
||||||
"NCSA",
|
"NCSA",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ pub const SELECTION_DRAG_ANGLE: f64 = 90.;
|
|||||||
pub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.;
|
pub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.;
|
||||||
pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.;
|
pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.;
|
||||||
pub const PIVOT_DIAMETER: f64 = 5.;
|
pub const PIVOT_DIAMETER: f64 = 5.;
|
||||||
|
pub const DOWEL_PIN_RADIUS: f64 = 4.;
|
||||||
|
|
||||||
// COMPASS ROSE
|
// COMPASS ROSE
|
||||||
pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.;
|
pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.;
|
||||||
@@ -133,8 +134,8 @@ pub const SCALE_EFFECT: f64 = 0.5;
|
|||||||
|
|
||||||
// COLORS
|
// COLORS
|
||||||
pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff";
|
pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff";
|
||||||
pub const COLOR_OVERLAY_BLUE_50: &str = "rgba(0, 168, 255, 0.5)";
|
|
||||||
pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848";
|
pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848";
|
||||||
|
pub const COLOR_OVERLAY_YELLOW_DULL: &str = "#d7ba8b";
|
||||||
pub const COLOR_OVERLAY_GREEN: &str = "#63ce63";
|
pub const COLOR_OVERLAY_GREEN: &str = "#63ce63";
|
||||||
pub const COLOR_OVERLAY_RED: &str = "#ef5454";
|
pub const COLOR_OVERLAY_RED: &str = "#ef5454";
|
||||||
pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";
|
pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ impl DispatcherMessageHandlers {
|
|||||||
/// The last occurrence of the message in the message queue is sufficient to ensure correct behavior.
|
/// The last occurrence of the message in the message queue is sufficient to ensure correct behavior.
|
||||||
/// In addition, these messages do not change any state in the backend (aside from caches).
|
/// In addition, these messages do not change any state in the backend (aside from caches).
|
||||||
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
||||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(NodeGraphMessageDiscriminant::SendGraph))),
|
|
||||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
|
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
|
||||||
PropertiesPanelMessageDiscriminant::Refresh,
|
PropertiesPanelMessageDiscriminant::Refresh,
|
||||||
))),
|
))),
|
||||||
@@ -141,6 +140,7 @@ impl Dispatcher {
|
|||||||
let graphene_std::renderer::RenderMetadata {
|
let graphene_std::renderer::RenderMetadata {
|
||||||
upstream_footprints: footprints,
|
upstream_footprints: footprints,
|
||||||
local_transforms,
|
local_transforms,
|
||||||
|
first_instance_source_id,
|
||||||
click_targets,
|
click_targets,
|
||||||
clip_targets,
|
clip_targets,
|
||||||
} = render_metadata;
|
} = render_metadata;
|
||||||
@@ -150,6 +150,7 @@ impl Dispatcher {
|
|||||||
DocumentMessage::UpdateUpstreamTransforms {
|
DocumentMessage::UpdateUpstreamTransforms {
|
||||||
upstream_footprints: footprints,
|
upstream_footprints: footprints,
|
||||||
local_transforms,
|
local_transforms,
|
||||||
|
first_instance_source_id,
|
||||||
},
|
},
|
||||||
DocumentMessage::UpdateClickTargets { click_targets },
|
DocumentMessage::UpdateClickTargets { click_targets },
|
||||||
DocumentMessage::UpdateClipTargets { clip_targets },
|
DocumentMessage::UpdateClipTargets { clip_targets },
|
||||||
|
|||||||
@@ -15,3 +15,4 @@ pub mod node_graph_executor;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod test_utils;
|
pub mod test_utils;
|
||||||
pub mod utility_traits;
|
pub mod utility_traits;
|
||||||
|
pub mod utility_types;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ enum AnimationState {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq)]
|
#[derive(Default, Debug, Clone, PartialEq, ExtractField)]
|
||||||
pub struct AnimationMessageHandler {
|
pub struct AnimationMessageHandler {
|
||||||
/// Used to re-send the UI on the next frame after playback starts
|
/// Used to re-send the UI on the next frame after playback starts
|
||||||
live_preview_recently_zero: bool,
|
live_preview_recently_zero: bool,
|
||||||
@@ -57,6 +57,7 @@ impl AnimationMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
|
impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
|
||||||
fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||||
match message {
|
match message {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
pub struct BroadcastMessageHandler {
|
pub struct BroadcastMessageHandler {
|
||||||
listeners: HashMap<BroadcastEvent, Vec<Message>>,
|
listeners: HashMap<BroadcastEvent, Vec<Message>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
|
impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
|
||||||
fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||||
match message {
|
match message {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use super::utility_types::MessageLoggingVerbosity;
|
use super::utility_types::MessageLoggingVerbosity;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, ExtractField)]
|
||||||
pub struct DebugMessageHandler {
|
pub struct DebugMessageHandler {
|
||||||
pub message_logging_verbosity: MessageLoggingVerbosity,
|
pub message_logging_verbosity: MessageLoggingVerbosity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<DebugMessage, ()> for DebugMessageHandler {
|
impl MessageHandler<DebugMessage, ()> for DebugMessageHandler {
|
||||||
fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||||
match message {
|
match message {
|
||||||
|
|||||||
@@ -2,19 +2,21 @@ use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArt
|
|||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
pub struct DialogMessageData<'a> {
|
pub struct DialogMessageData<'a> {
|
||||||
pub portfolio: &'a PortfolioMessageHandler,
|
pub portfolio: &'a PortfolioMessageHandler,
|
||||||
pub preferences: &'a PreferencesMessageHandler,
|
pub preferences: &'a PreferencesMessageHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores the dialogs which require state. These are the ones that have their own message handlers, and are not the ones defined in `simple_dialogs`.
|
/// Stores the dialogs which require state. These are the ones that have their own message handlers, and are not the ones defined in `simple_dialogs`.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone, ExtractField)]
|
||||||
pub struct DialogMessageHandler {
|
pub struct DialogMessageHandler {
|
||||||
export_dialog: ExportDialogMessageHandler,
|
export_dialog: ExportDialogMessageHandler,
|
||||||
new_document_dialog: NewDocumentDialogMessageHandler,
|
new_document_dialog: NewDocumentDialogMessageHandler,
|
||||||
preferences_dialog: PreferencesDialogMessageHandler,
|
preferences_dialog: PreferencesDialogMessageHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<DialogMessage, DialogMessageData<'_>> for DialogMessageHandler {
|
impl MessageHandler<DialogMessage, DialogMessageData<'_>> for DialogMessageHandler {
|
||||||
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, data: DialogMessageData) {
|
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, data: DialogMessageData) {
|
||||||
let DialogMessageData { portfolio, preferences } = data;
|
let DialogMessageData { portfolio, preferences } = data;
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
|||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
pub struct ExportDialogMessageData<'a> {
|
pub struct ExportDialogMessageData<'a> {
|
||||||
pub portfolio: &'a PortfolioMessageHandler,
|
pub portfolio: &'a PortfolioMessageHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A dialog to allow users to customize their file export.
|
/// A dialog to allow users to customize their file export.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, ExtractField)]
|
||||||
pub struct ExportDialogMessageHandler {
|
pub struct ExportDialogMessageHandler {
|
||||||
pub file_type: FileType,
|
pub file_type: FileType,
|
||||||
pub scale_factor: f64,
|
pub scale_factor: f64,
|
||||||
@@ -31,6 +32,7 @@ impl Default for ExportDialogMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<ExportDialogMessage, ExportDialogMessageData<'_>> for ExportDialogMessageHandler {
|
impl MessageHandler<ExportDialogMessage, ExportDialogMessageData<'_>> for ExportDialogMessageHandler {
|
||||||
fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque<Message>, data: ExportDialogMessageData) {
|
fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque<Message>, data: ExportDialogMessageData) {
|
||||||
let ExportDialogMessageData { portfolio } = data;
|
let ExportDialogMessageData { portfolio } = data;
|
||||||
|
|||||||
+2
-1
@@ -4,13 +4,14 @@ use glam::{IVec2, UVec2};
|
|||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
|
|
||||||
/// A dialog to allow users to set some initial options about a new document.
|
/// A dialog to allow users to set some initial options about a new document.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
pub struct NewDocumentDialogMessageHandler {
|
pub struct NewDocumentDialogMessageHandler {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub infinite: bool,
|
pub infinite: bool,
|
||||||
pub dimensions: UVec2,
|
pub dimensions: UVec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHandler {
|
impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHandler {
|
||||||
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||||
match message {
|
match message {
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
use crate::consts::{VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE};
|
use crate::consts::{VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE};
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
|
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||||
use crate::messages::preferences::SelectionMode;
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
pub struct PreferencesDialogMessageData<'a> {
|
pub struct PreferencesDialogMessageData<'a> {
|
||||||
pub preferences: &'a PreferencesMessageHandler,
|
pub preferences: &'a PreferencesMessageHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A dialog to allow users to customize Graphite editor options
|
/// A dialog to allow users to customize Graphite editor options
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
pub struct PreferencesDialogMessageHandler {}
|
pub struct PreferencesDialogMessageHandler {}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<PreferencesDialogMessage, PreferencesDialogMessageData<'_>> for PreferencesDialogMessageHandler {
|
impl MessageHandler<PreferencesDialogMessage, PreferencesDialogMessageData<'_>> for PreferencesDialogMessageHandler {
|
||||||
fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque<Message>, data: PreferencesDialogMessageData) {
|
fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque<Message>, data: PreferencesDialogMessageData) {
|
||||||
let PreferencesDialogMessageData { preferences } = data;
|
let PreferencesDialogMessageData { preferences } = data;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
|
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::node_graph::utility_types::{
|
use crate::messages::portfolio::document::node_graph::utility_types::{
|
||||||
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, FrontendNodeWire, Transform, WirePath,
|
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform,
|
||||||
};
|
};
|
||||||
use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer};
|
use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer};
|
||||||
|
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::utility_types::HintData;
|
use crate::messages::tool::utility_types::HintData;
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
@@ -250,12 +251,16 @@ pub enum FrontendMessage {
|
|||||||
UpdateMouseCursor {
|
UpdateMouseCursor {
|
||||||
cursor: MouseCursorIcon,
|
cursor: MouseCursorIcon,
|
||||||
},
|
},
|
||||||
UpdateNodeGraph {
|
UpdateNodeGraphNodes {
|
||||||
nodes: Vec<FrontendNode>,
|
nodes: Vec<FrontendNode>,
|
||||||
wires: Vec<FrontendNodeWire>,
|
|
||||||
#[serde(rename = "wiresDirectNotGridAligned")]
|
|
||||||
wires_direct_not_grid_aligned: bool,
|
|
||||||
},
|
},
|
||||||
|
UpdateVisibleNodes {
|
||||||
|
nodes: Vec<NodeId>,
|
||||||
|
},
|
||||||
|
UpdateNodeGraphWires {
|
||||||
|
wires: Vec<WirePathUpdate>,
|
||||||
|
},
|
||||||
|
ClearAllNodeGraphWires,
|
||||||
UpdateNodeGraphControlBarLayout {
|
UpdateNodeGraphControlBarLayout {
|
||||||
#[serde(rename = "layoutTarget")]
|
#[serde(rename = "layoutTarget")]
|
||||||
layout_target: LayoutTarget,
|
layout_target: LayoutTarget,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, ExtractField)]
|
||||||
pub struct GlobalsMessageHandler {}
|
pub struct GlobalsMessageHandler {}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<GlobalsMessage, ()> for GlobalsMessageHandler {
|
impl MessageHandler<GlobalsMessage, ()> for GlobalsMessageHandler {
|
||||||
fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque<Message>, _data: ()) {
|
fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque<Message>, _data: ()) {
|
||||||
match message {
|
match message {
|
||||||
|
|||||||
@@ -6,16 +6,18 @@ use crate::messages::portfolio::utility_types::KeyboardPlatformLayout;
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
pub struct InputMapperMessageData<'a> {
|
pub struct InputMapperMessageData<'a> {
|
||||||
pub input: &'a InputPreprocessorMessageHandler,
|
pub input: &'a InputPreprocessorMessageHandler,
|
||||||
pub actions: ActionList,
|
pub actions: ActionList,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, ExtractField)]
|
||||||
pub struct InputMapperMessageHandler {
|
pub struct InputMapperMessageHandler {
|
||||||
mapping: Mapping,
|
mapping: Mapping,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<InputMapperMessage, InputMapperMessageData<'_>> for InputMapperMessageHandler {
|
impl MessageHandler<InputMapperMessage, InputMapperMessageData<'_>> for InputMapperMessageHandler {
|
||||||
fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque<Message>, data: InputMapperMessageData) {
|
fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque<Message>, data: InputMapperMessageData) {
|
||||||
let InputMapperMessageData { input, actions } = data;
|
let InputMapperMessageData { input, actions } = data;
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ pub fn input_mappings() -> Mapping {
|
|||||||
entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete),
|
entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete),
|
||||||
entry!(KeyUp(MouseLeft); action_dispatch=PathToolMessage::DragStop { extend_selection: Shift, shrink_selection: Alt }),
|
entry!(KeyUp(MouseLeft); action_dispatch=PathToolMessage::DragStop { extend_selection: Shift, shrink_selection: Alt }),
|
||||||
entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { extend_selection: Shift, shrink_selection: Alt }),
|
entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { extend_selection: Shift, shrink_selection: Alt }),
|
||||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::FlipSmoothSharp),
|
entry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::DoubleClick { extend_selection: Shift, shrink_selection: Alt }),
|
||||||
entry!(KeyDown(ArrowRight); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: 0. }),
|
entry!(KeyDown(ArrowRight); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: 0. }),
|
||||||
entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }),
|
entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }),
|
||||||
entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
|
entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
|
||||||
|
|||||||
@@ -2,16 +2,18 @@ use crate::messages::input_mapper::input_mapper_message_handler::InputMapperMess
|
|||||||
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
|
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
pub struct KeyMappingMessageData<'a> {
|
pub struct KeyMappingMessageData<'a> {
|
||||||
pub input: &'a InputPreprocessorMessageHandler,
|
pub input: &'a InputPreprocessorMessageHandler,
|
||||||
pub actions: ActionList,
|
pub actions: ActionList,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, ExtractField)]
|
||||||
pub struct KeyMappingMessageHandler {
|
pub struct KeyMappingMessageHandler {
|
||||||
mapping_handler: InputMapperMessageHandler,
|
mapping_handler: InputMapperMessageHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<KeyMappingMessage, KeyMappingMessageData<'_>> for KeyMappingMessageHandler {
|
impl MessageHandler<KeyMappingMessage, KeyMappingMessageData<'_>> for KeyMappingMessageHandler {
|
||||||
fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque<Message>, data: KeyMappingMessageData) {
|
fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque<Message>, data: KeyMappingMessageData) {
|
||||||
let KeyMappingMessageData { input, actions } = data;
|
let KeyMappingMessageData { input, actions } = data;
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ use crate::messages::prelude::*;
|
|||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
pub struct InputPreprocessorMessageData {
|
pub struct InputPreprocessorMessageData {
|
||||||
pub keyboard_platform: KeyboardPlatformLayout,
|
pub keyboard_platform: KeyboardPlatformLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, ExtractField)]
|
||||||
pub struct InputPreprocessorMessageHandler {
|
pub struct InputPreprocessorMessageHandler {
|
||||||
pub frame_time: FrameTimeInfo,
|
pub frame_time: FrameTimeInfo,
|
||||||
pub time: u64,
|
pub time: u64,
|
||||||
@@ -19,6 +20,7 @@ pub struct InputPreprocessorMessageHandler {
|
|||||||
pub viewport_bounds: ViewportBounds,
|
pub viewport_bounds: ViewportBounds,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for InputPreprocessorMessageHandler {
|
impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for InputPreprocessorMessageHandler {
|
||||||
fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, data: InputPreprocessorMessageData) {
|
fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, data: InputPreprocessorMessageData) {
|
||||||
let InputPreprocessorMessageData { keyboard_platform } = data;
|
let InputPreprocessorMessageData { keyboard_platform } = data;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use graphene_std::text::Font;
|
|||||||
use graphene_std::vector::style::{FillChoice, GradientStops};
|
use graphene_std::vector::style::{FillChoice, GradientStops};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
pub struct LayoutMessageHandler {
|
pub struct LayoutMessageHandler {
|
||||||
layouts: [Layout; LayoutTarget::LayoutTargetLength as usize],
|
layouts: [Layout; LayoutTarget::LayoutTargetLength as usize],
|
||||||
}
|
}
|
||||||
@@ -342,6 +342,15 @@ impl LayoutMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn custom_data() -> MessageData {
|
||||||
|
// TODO: When <https://github.com/dtolnay/proc-macro2/issues/503> is resolved and released,
|
||||||
|
// TODO: use <https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.line> to get
|
||||||
|
// TODO: the line number instead of hardcoding it to the magic number on the following line.
|
||||||
|
// TODO: Also, utilize the line number in the actual output, since it is currently unused.
|
||||||
|
MessageData::new(String::from("Function"), vec![(String::from("Fn(&MessageDiscriminant) -> Option<KeysGroup>"), 350)], file!())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[message_handler_data(CustomData)]
|
||||||
impl<F: Fn(&MessageDiscriminant) -> Option<KeysGroup>> MessageHandler<LayoutMessage, F> for LayoutMessageHandler {
|
impl<F: Fn(&MessageDiscriminant) -> Option<KeysGroup>> MessageHandler<LayoutMessage, F> for LayoutMessageHandler {
|
||||||
fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque<Message>, action_input_mapping: F) {
|
fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque<Message>, action_input_mapping: F) {
|
||||||
match message {
|
match message {
|
||||||
|
|||||||
@@ -471,6 +471,8 @@ pub struct ReferencePointInput {
|
|||||||
|
|
||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
|
|
||||||
|
pub tooltip: String,
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
|
|||||||
@@ -45,3 +45,86 @@ impl specta::Type for MessageDiscriminant {
|
|||||||
specta::DataType::Any
|
specta::DataType::Any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_message_tree() {
|
||||||
|
let result = Message::build_message_tree();
|
||||||
|
let mut file = std::fs::File::create("../hierarchical_message_system_tree.txt").unwrap();
|
||||||
|
file.write_all(format!("{} `{}`\n", result.name(), result.path()).as_bytes()).unwrap();
|
||||||
|
if let Some(variants) = result.variants() {
|
||||||
|
for (i, variant) in variants.iter().enumerate() {
|
||||||
|
let is_last = i == variants.len() - 1;
|
||||||
|
print_tree_node(variant, "", is_last, &mut file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) {
|
||||||
|
// Print the current node
|
||||||
|
let (branch, child_prefix) = if tree.has_message_handler_data_fields() || tree.has_message_handler_fields() {
|
||||||
|
("├── ", format!("{}│ ", prefix))
|
||||||
|
} else {
|
||||||
|
if is_last {
|
||||||
|
("└── ", format!("{} ", prefix))
|
||||||
|
} else {
|
||||||
|
("├── ", format!("{}│ ", prefix))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if tree.path().is_empty() {
|
||||||
|
file.write_all(format!("{}{}{}\n", prefix, branch, tree.name()).as_bytes()).unwrap();
|
||||||
|
} else {
|
||||||
|
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, tree.name(), tree.path()).as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print children if any
|
||||||
|
if let Some(variants) = tree.variants() {
|
||||||
|
let len = variants.len();
|
||||||
|
for (i, variant) in variants.iter().enumerate() {
|
||||||
|
let is_last_child = i == len - 1;
|
||||||
|
print_tree_node(variant, &child_prefix, is_last_child, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print handler field if any
|
||||||
|
if let Some(data) = tree.message_handler_fields() {
|
||||||
|
let len = data.fields().len();
|
||||||
|
let (branch, child_prefix) = if tree.has_message_handler_data_fields() {
|
||||||
|
("├── ", format!("{}│ ", prefix))
|
||||||
|
} else {
|
||||||
|
("└── ", format!("{} ", prefix))
|
||||||
|
};
|
||||||
|
if data.path().is_empty() {
|
||||||
|
file.write_all(format!("{}{}{}\n", prefix, branch, data.name()).as_bytes()).unwrap();
|
||||||
|
} else {
|
||||||
|
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, data.name(), data.path()).as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
for (i, field) in data.fields().iter().enumerate() {
|
||||||
|
let is_last_field = i == len - 1;
|
||||||
|
let branch = if is_last_field { "└── " } else { "├── " };
|
||||||
|
|
||||||
|
file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print data field if any
|
||||||
|
if let Some(data) = tree.message_handler_data_fields() {
|
||||||
|
let len = data.fields().len();
|
||||||
|
if data.path().is_empty() {
|
||||||
|
file.write_all(format!("{}{}{}\n", prefix, "└── ", data.name()).as_bytes()).unwrap();
|
||||||
|
} else {
|
||||||
|
file.write_all(format!("{}{}{} `{}`\n", prefix, "└── ", data.name(), data.path()).as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
for (i, field) in data.fields().iter().enumerate() {
|
||||||
|
let is_last_field = i == len - 1;
|
||||||
|
let branch = if is_last_field { "└── " } else { "├── " };
|
||||||
|
file.write_all(format!("{}{}{}\n", format!("{} ", prefix), branch, field.0).as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ pub enum DocumentMessage {
|
|||||||
UpdateUpstreamTransforms {
|
UpdateUpstreamTransforms {
|
||||||
upstream_footprints: HashMap<NodeId, Footprint>,
|
upstream_footprints: HashMap<NodeId, Footprint>,
|
||||||
local_transforms: HashMap<NodeId, DAffine2>,
|
local_transforms: HashMap<NodeId, DAffine2>,
|
||||||
|
first_instance_source_id: HashMap<NodeId, Option<NodeId>>,
|
||||||
},
|
},
|
||||||
UpdateClickTargets {
|
UpdateClickTargets {
|
||||||
click_targets: HashMap<NodeId, Vec<ClickTarget>>,
|
click_targets: HashMap<NodeId, Vec<ClickTarget>>,
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
|||||||
use graphene_std::vector::style::ViewMode;
|
use graphene_std::vector::style::ViewMode;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
pub struct DocumentMessageData<'a> {
|
pub struct DocumentMessageData<'a> {
|
||||||
pub document_id: DocumentId,
|
pub document_id: DocumentId,
|
||||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||||
@@ -48,7 +49,7 @@ pub struct DocumentMessageData<'a> {
|
|||||||
pub device_pixel_ratio: f64,
|
pub device_pixel_ratio: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct DocumentMessageHandler {
|
pub struct DocumentMessageHandler {
|
||||||
// ======================
|
// ======================
|
||||||
@@ -168,6 +169,7 @@ impl Default for DocumentMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessageHandler {
|
impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessageHandler {
|
||||||
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, data: DocumentMessageData) {
|
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, data: DocumentMessageData) {
|
||||||
let DocumentMessageData {
|
let DocumentMessageData {
|
||||||
@@ -444,6 +446,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
DocumentMessage::EnterNestedNetwork { node_id } => {
|
DocumentMessage::EnterNestedNetwork { node_id } => {
|
||||||
self.breadcrumb_network_path.push(node_id);
|
self.breadcrumb_network_path.push(node_id);
|
||||||
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
|
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
|
||||||
|
responses.add(NodeGraphMessage::UnloadWires);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
responses.add(DocumentMessage::ZoomCanvasToFitAll);
|
responses.add(DocumentMessage::ZoomCanvasToFitAll);
|
||||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||||
@@ -473,9 +476,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
self.breadcrumb_network_path.pop();
|
self.breadcrumb_network_path.pop();
|
||||||
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
|
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
|
||||||
}
|
}
|
||||||
|
responses.add(NodeGraphMessage::UnloadWires);
|
||||||
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
responses.add(DocumentMessage::PTZUpdate);
|
responses.add(DocumentMessage::PTZUpdate);
|
||||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
|
||||||
}
|
}
|
||||||
DocumentMessage::FlipSelectedLayers { flip_axis } => {
|
DocumentMessage::FlipSelectedLayers { flip_axis } => {
|
||||||
let scale = match flip_axis {
|
let scale = match flip_axis {
|
||||||
@@ -525,6 +529,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DocumentMessage::GraphViewOverlay { open } => {
|
DocumentMessage::GraphViewOverlay { open } => {
|
||||||
|
let opened = !self.graph_view_overlay_open && open;
|
||||||
self.graph_view_overlay_open = open;
|
self.graph_view_overlay_open = open;
|
||||||
|
|
||||||
responses.add(FrontendMessage::UpdateGraphViewOverlay { open });
|
responses.add(FrontendMessage::UpdateGraphViewOverlay { open });
|
||||||
@@ -537,6 +542,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
|
|
||||||
responses.add(DocumentMessage::RenderRulers);
|
responses.add(DocumentMessage::RenderRulers);
|
||||||
responses.add(DocumentMessage::RenderScrollbars);
|
responses.add(DocumentMessage::RenderScrollbars);
|
||||||
|
if opened {
|
||||||
|
responses.add(NodeGraphMessage::UnloadWires);
|
||||||
|
}
|
||||||
if open {
|
if open {
|
||||||
responses.add(ToolMessage::DeactivateTools);
|
responses.add(ToolMessage::DeactivateTools);
|
||||||
responses.add(OverlaysMessage::Draw); // Clear the overlays
|
responses.add(OverlaysMessage::Draw); // Clear the overlays
|
||||||
@@ -744,6 +752,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
// Nudge translation without resizing
|
// Nudge translation without resizing
|
||||||
if !resize {
|
if !resize {
|
||||||
let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y)));
|
let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y)));
|
||||||
|
responses.add(SelectToolMessage::ShiftSelectedNodes { offset: transform.translation });
|
||||||
|
|
||||||
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
|
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
|
||||||
responses.add(GraphOperationMessage::TransformChange {
|
responses.add(GraphOperationMessage::TransformChange {
|
||||||
@@ -1179,6 +1188,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
OverlaysType::HoverOutline => visibility_settings.hover_outline = visible,
|
OverlaysType::HoverOutline => visibility_settings.hover_outline = visible,
|
||||||
OverlaysType::SelectionOutline => visibility_settings.selection_outline = visible,
|
OverlaysType::SelectionOutline => visibility_settings.selection_outline = visible,
|
||||||
OverlaysType::Pivot => visibility_settings.pivot = visible,
|
OverlaysType::Pivot => visibility_settings.pivot = visible,
|
||||||
|
OverlaysType::Origin => visibility_settings.origin = visible,
|
||||||
OverlaysType::Path => visibility_settings.path = visible,
|
OverlaysType::Path => visibility_settings.path = visible,
|
||||||
OverlaysType::Anchors => {
|
OverlaysType::Anchors => {
|
||||||
visibility_settings.anchors = visible;
|
visibility_settings.anchors = visible;
|
||||||
@@ -1299,8 +1309,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
DocumentMessage::UpdateUpstreamTransforms {
|
DocumentMessage::UpdateUpstreamTransforms {
|
||||||
upstream_footprints,
|
upstream_footprints,
|
||||||
local_transforms,
|
local_transforms,
|
||||||
|
first_instance_source_id,
|
||||||
} => {
|
} => {
|
||||||
self.network_interface.update_transforms(upstream_footprints, local_transforms);
|
self.network_interface.update_transforms(upstream_footprints, local_transforms);
|
||||||
|
self.network_interface.update_first_instance_source_id(first_instance_source_id);
|
||||||
}
|
}
|
||||||
DocumentMessage::UpdateClickTargets { click_targets } => {
|
DocumentMessage::UpdateClickTargets { click_targets } => {
|
||||||
// TODO: Allow non layer nodes to have click targets
|
// TODO: Allow non layer nodes to have click targets
|
||||||
@@ -1708,6 +1720,14 @@ impl DocumentMessageHandler {
|
|||||||
.reduce(graphene_std::renderer::Quad::combine_bounds)
|
.reduce(graphene_std::renderer::Quad::combine_bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn selected_visible_and_unlock_layers_bounding_box_document(&self) -> Option<[DVec2; 2]> {
|
||||||
|
self.network_interface
|
||||||
|
.selected_nodes()
|
||||||
|
.selected_visible_and_unlocked_layers(&self.network_interface)
|
||||||
|
.map(|layer| self.metadata().nonzero_bounding_box(layer))
|
||||||
|
.reduce(graphene_std::renderer::Quad::combine_bounds)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn document_network(&self) -> &NodeNetwork {
|
pub fn document_network(&self) -> &NodeNetwork {
|
||||||
self.network_interface.document_network()
|
self.network_interface.document_network()
|
||||||
}
|
}
|
||||||
@@ -1878,6 +1898,7 @@ impl DocumentMessageHandler {
|
|||||||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||||
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
||||||
// TODO: Remove once the footprint is used to load the imports/export distances from the edge
|
// TODO: Remove once the footprint is used to load the imports/export distances from the edge
|
||||||
|
responses.add(NodeGraphMessage::UnloadWires);
|
||||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||||
responses.add(Message::StartBuffer);
|
responses.add(Message::StartBuffer);
|
||||||
Some(previous_network)
|
Some(previous_network)
|
||||||
@@ -1909,7 +1930,8 @@ impl DocumentMessageHandler {
|
|||||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||||
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
||||||
|
responses.add(NodeGraphMessage::UnloadWires);
|
||||||
|
responses.add(NodeGraphMessage::SendWires);
|
||||||
Some(previous_network)
|
Some(previous_network)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2066,7 +2088,7 @@ impl DocumentMessageHandler {
|
|||||||
/// Loads all of the fonts in the document.
|
/// Loads all of the fonts in the document.
|
||||||
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) {
|
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) {
|
||||||
let mut fonts = HashSet::new();
|
let mut fonts = HashSet::new();
|
||||||
for (_node_id, node) in self.document_network().recursive_nodes() {
|
for (_node_id, node, _) in self.document_network().recursive_nodes() {
|
||||||
for input in &node.inputs {
|
for input in &node.inputs {
|
||||||
if let Some(TaggedValue::Font(font)) = input.as_value() {
|
if let Some(TaggedValue::Font(font)) = input.as_value() {
|
||||||
fonts.insert(font.clone());
|
fonts.insert(font.clone());
|
||||||
@@ -2259,6 +2281,24 @@ impl DocumentMessageHandler {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
LayoutGroup::Row {
|
||||||
|
widgets: {
|
||||||
|
let mut checkbox_id = CheckboxId::default();
|
||||||
|
vec![
|
||||||
|
CheckboxInput::new(self.overlays_visibility_settings.pivot)
|
||||||
|
.on_update(|optional_input: &CheckboxInput| {
|
||||||
|
DocumentMessage::SetOverlaysVisibility {
|
||||||
|
visible: optional_input.checked,
|
||||||
|
overlays_type: Some(OverlaysType::Origin),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.for_label(checkbox_id.clone())
|
||||||
|
.widget_holder(),
|
||||||
|
TextLabel::new("Transform Origin".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
LayoutGroup::Row {
|
LayoutGroup::Row {
|
||||||
widgets: {
|
widgets: {
|
||||||
let mut checkbox_id = CheckboxId::default();
|
let mut checkbox_id = CheckboxId::default();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
|||||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
|
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use bezier_rs::Subpath;
|
use bezier_rs::Subpath;
|
||||||
use glam::{DAffine2, DVec2, IVec2};
|
use glam::{DAffine2, IVec2};
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::Artboard;
|
use graphene_std::Artboard;
|
||||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||||
@@ -52,10 +52,6 @@ pub enum GraphOperationMessage {
|
|||||||
transform_in: TransformIn,
|
transform_in: TransformIn,
|
||||||
skip_rerender: bool,
|
skip_rerender: bool,
|
||||||
},
|
},
|
||||||
TransformSetPivot {
|
|
||||||
layer: LayerNodeIdentifier,
|
|
||||||
pivot: DVec2,
|
|
||||||
},
|
|
||||||
Vector {
|
Vector {
|
||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
modification_type: VectorModificationType,
|
modification_type: VectorModificationType,
|
||||||
|
|||||||
+3
-10
@@ -21,17 +21,19 @@ struct ArtboardInfo {
|
|||||||
merge_node: NodeId,
|
merge_node: NodeId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
pub struct GraphOperationMessageData<'a> {
|
pub struct GraphOperationMessageData<'a> {
|
||||||
pub network_interface: &'a mut NodeNetworkInterface,
|
pub network_interface: &'a mut NodeNetworkInterface,
|
||||||
pub collapsed: &'a mut CollapsedLayers,
|
pub collapsed: &'a mut CollapsedLayers,
|
||||||
pub node_graph: &'a mut NodeGraphMessageHandler,
|
pub node_graph: &'a mut NodeGraphMessageHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||||
pub struct GraphOperationMessageHandler {}
|
pub struct GraphOperationMessageHandler {}
|
||||||
|
|
||||||
// GraphOperationMessageHandler always modified the document network. This is so changes to the layers panel will only affect the document network.
|
// GraphOperationMessageHandler always modified the document network. This is so changes to the layers panel will only affect the document network.
|
||||||
// For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network.
|
// For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network.
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for GraphOperationMessageHandler {
|
impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for GraphOperationMessageHandler {
|
||||||
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, data: GraphOperationMessageData) {
|
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, data: GraphOperationMessageData) {
|
||||||
let network_interface = data.network_interface;
|
let network_interface = data.network_interface;
|
||||||
@@ -89,15 +91,6 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
|||||||
modify_inputs.transform_set(transform, transform_in, skip_rerender);
|
modify_inputs.transform_set(transform, transform_in, skip_rerender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GraphOperationMessage::TransformSetPivot { layer, pivot } => {
|
|
||||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
|
||||||
log::error!("Cannot run TransformSetPivot on ROOT_PARENT");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
|
||||||
modify_inputs.pivot_set(pivot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GraphOperationMessage::Vector { layer, modification_type } => {
|
GraphOperationMessage::Vector { layer, modification_type } => {
|
||||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||||
log::error!("Cannot run Vector on ROOT_PARENT");
|
log::error!("Cannot run Vector on ROOT_PARENT");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
|||||||
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
|
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use bezier_rs::Subpath;
|
use bezier_rs::Subpath;
|
||||||
use glam::{DAffine2, DVec2, IVec2};
|
use glam::{DAffine2, IVec2};
|
||||||
use graph_craft::concrete;
|
use graph_craft::concrete;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{NodeId, NodeInput};
|
use graph_craft::document::{NodeId, NodeInput};
|
||||||
@@ -97,6 +97,8 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let layer_input_connector = post_node_input_connector.clone();
|
||||||
|
|
||||||
// Sink post_node down to the end of the non layer chain that feeds into post_node, such that pre_node is the layer node at insert_index + 1, or None if insert_index is the last layer
|
// Sink post_node down to the end of the non layer chain that feeds into post_node, such that pre_node is the layer node at insert_index + 1, or None if insert_index is the last layer
|
||||||
loop {
|
loop {
|
||||||
let pre_node_output_connector = network_interface.upstream_output_connector(&post_node_input_connector, &[]);
|
let pre_node_output_connector = network_interface.upstream_output_connector(&post_node_input_connector, &[]);
|
||||||
@@ -105,6 +107,11 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
Some(OutputConnector::Node { node_id: pre_node_id, .. }) if !network_interface.is_layer(&pre_node_id, &[]) => {
|
Some(OutputConnector::Node { node_id: pre_node_id, .. }) if !network_interface.is_layer(&pre_node_id, &[]) => {
|
||||||
// Update post_node_input_connector for the next iteration
|
// Update post_node_input_connector for the next iteration
|
||||||
post_node_input_connector = InputConnector::node(pre_node_id, 0);
|
post_node_input_connector = InputConnector::node(pre_node_id, 0);
|
||||||
|
// Insert directly under layer if moving to the end of a layer stack that ends with a non layer node that does not have an exposed primary input
|
||||||
|
let primary_is_exposed = network_interface.input_from_connector(&post_node_input_connector, &[]).is_some_and(|input| input.is_exposed());
|
||||||
|
if !primary_is_exposed {
|
||||||
|
return layer_input_connector;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => break, // Break if pre_node_output_connector is None or if pre_node_id is a layer
|
_ => break, // Break if pre_node_output_connector is None or if pre_node_id is a layer
|
||||||
}
|
}
|
||||||
@@ -451,12 +458,6 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pivot_set(&mut self, new_pivot: DVec2) {
|
|
||||||
let Some(transform_node_id) = self.existing_node_id("Transform", true) else { return };
|
|
||||||
|
|
||||||
self.set_input_with_refresh(InputConnector::node(transform_node_id, 5), NodeInput::value(TaggedValue::DVec2(new_pivot), false), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vector_modify(&mut self, modification_type: VectorModificationType) {
|
pub fn vector_modify(&mut self, modification_type: VectorModificationType) {
|
||||||
let Some(path_node_id) = self.existing_node_id("Path", true) else { return };
|
let Some(path_node_id) = self.existing_node_id("Path", true) else { return };
|
||||||
self.network_interface.vector_modify(&path_node_id, modification_type);
|
self.network_interface.vector_modify(&path_node_id, modification_type);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
|||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
pub struct NavigationMessageData<'a> {
|
pub struct NavigationMessageData<'a> {
|
||||||
pub network_interface: &'a mut NodeNetworkInterface,
|
pub network_interface: &'a mut NodeNetworkInterface,
|
||||||
pub breadcrumb_network_path: &'a [NodeId],
|
pub breadcrumb_network_path: &'a [NodeId],
|
||||||
@@ -23,7 +24,7 @@ pub struct NavigationMessageData<'a> {
|
|||||||
pub preferences: &'a PreferencesMessageHandler,
|
pub preferences: &'a PreferencesMessageHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Default)]
|
#[derive(Debug, Clone, PartialEq, Default, ExtractField)]
|
||||||
pub struct NavigationMessageHandler {
|
pub struct NavigationMessageHandler {
|
||||||
navigation_operation: NavigationOperation,
|
navigation_operation: NavigationOperation,
|
||||||
mouse_position: ViewportPosition,
|
mouse_position: ViewportPosition,
|
||||||
@@ -31,6 +32,7 @@ pub struct NavigationMessageHandler {
|
|||||||
abortable_pan_start: Option<f64>,
|
abortable_pan_start: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for NavigationMessageHandler {
|
impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for NavigationMessageHandler {
|
||||||
fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque<Message>, data: NavigationMessageData) {
|
fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque<Message>, data: NavigationMessageData) {
|
||||||
let NavigationMessageData {
|
let NavigationMessageData {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -1,5 +1,5 @@
|
|||||||
use super::DocumentNodeDefinition;
|
use super::DocumentNodeDefinition;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, NodeTemplate, PropertiesRow, WidgetOverride};
|
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, InputMetadata, NodeTemplate, WidgetOverride};
|
||||||
use graph_craft::ProtoNodeIdentifier;
|
use graph_craft::ProtoNodeIdentifier;
|
||||||
use graph_craft::document::*;
|
use graph_craft::document::*;
|
||||||
use graphene_std::registry::*;
|
use graphene_std::registry::*;
|
||||||
@@ -21,7 +21,7 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap();
|
let node_registry = NODE_REGISTRY.lock().unwrap();
|
||||||
'outer: for (id, metadata) in NODE_METADATA.lock().unwrap().iter() {
|
'outer: for (id, metadata) in NODE_METADATA.lock().unwrap().iter() {
|
||||||
for node in custom.iter() {
|
for node in custom.iter() {
|
||||||
let DocumentNodeDefinition {
|
let DocumentNodeDefinition {
|
||||||
@@ -32,7 +32,7 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
|
|||||||
..
|
..
|
||||||
} = node;
|
} = node;
|
||||||
match implementation {
|
match implementation {
|
||||||
DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == id => continue 'outer,
|
DocumentNodeImplementation::ProtoNode(name) if name == id => continue 'outer,
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,13 +67,13 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
|
|||||||
},
|
},
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
// TODO: Store information for input overrides in the node macro
|
// TODO: Store information for input overrides in the node macro
|
||||||
input_properties: fields
|
input_metadata: fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f| match f.widget_override {
|
.map(|f| match f.widget_override {
|
||||||
RegistryWidgetOverride::None => (f.name, f.description).into(),
|
RegistryWidgetOverride::None => (f.name, f.description).into(),
|
||||||
RegistryWidgetOverride::Hidden => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Hidden),
|
RegistryWidgetOverride::Hidden => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Hidden),
|
||||||
RegistryWidgetOverride::String(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::String(str.to_string())),
|
RegistryWidgetOverride::String(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::String(str.to_string())),
|
||||||
RegistryWidgetOverride::Custom(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
|
RegistryWidgetOverride::Custom(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
output_names: vec![output_type.to_string()],
|
output_names: vec![output_type.to_string()],
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ pub enum NodeGraphMessage {
|
|||||||
node_id: Option<NodeId>,
|
node_id: Option<NodeId>,
|
||||||
node_type: String,
|
node_type: String,
|
||||||
xy: Option<(i32, i32)>,
|
xy: Option<(i32, i32)>,
|
||||||
|
add_transaction: bool,
|
||||||
},
|
},
|
||||||
CreateWire {
|
CreateWire {
|
||||||
output_connector: OutputConnector,
|
output_connector: OutputConnector,
|
||||||
@@ -123,6 +124,9 @@ pub enum NodeGraphMessage {
|
|||||||
},
|
},
|
||||||
SendClickTargets,
|
SendClickTargets,
|
||||||
EndSendClickTargets,
|
EndSendClickTargets,
|
||||||
|
UnloadWires,
|
||||||
|
SendWires,
|
||||||
|
UpdateVisibleNodes,
|
||||||
SendGraph,
|
SendGraph,
|
||||||
SetGridAlignedEdges,
|
SetGridAlignedEdges,
|
||||||
SetInputValue {
|
SetInputValue {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeWire, WirePath};
|
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode};
|
||||||
use super::{document_node_definitions, node_properties};
|
use super::{document_node_definitions, node_properties};
|
||||||
use crate::consts::GRID_SIZE;
|
use crate::consts::GRID_SIZE;
|
||||||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||||
@@ -13,6 +13,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{
|
|||||||
self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource,
|
self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource,
|
||||||
};
|
};
|
||||||
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};
|
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};
|
||||||
|
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;
|
use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;
|
||||||
@@ -26,7 +27,7 @@ use graphene_std::*;
|
|||||||
use renderer::Quad;
|
use renderer::Quad;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, ExtractField)]
|
||||||
pub struct NodeGraphHandlerData<'a> {
|
pub struct NodeGraphHandlerData<'a> {
|
||||||
pub network_interface: &'a mut NodeNetworkInterface,
|
pub network_interface: &'a mut NodeNetworkInterface,
|
||||||
pub selection_network_path: &'a [NodeId],
|
pub selection_network_path: &'a [NodeId],
|
||||||
@@ -40,7 +41,7 @@ pub struct NodeGraphHandlerData<'a> {
|
|||||||
pub preferences: &'a PreferencesMessageHandler,
|
pub preferences: &'a PreferencesMessageHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, ExtractField)]
|
||||||
pub struct NodeGraphMessageHandler {
|
pub struct NodeGraphMessageHandler {
|
||||||
// TODO: Remove network and move to NodeNetworkInterface
|
// TODO: Remove network and move to NodeNetworkInterface
|
||||||
pub network: Vec<NodeId>,
|
pub network: Vec<NodeId>,
|
||||||
@@ -67,6 +68,7 @@ pub struct NodeGraphMessageHandler {
|
|||||||
select_if_not_dragged: Option<NodeId>,
|
select_if_not_dragged: Option<NodeId>,
|
||||||
/// The start of the dragged line (cannot be moved), stored in node graph coordinates
|
/// The start of the dragged line (cannot be moved), stored in node graph coordinates
|
||||||
pub wire_in_progress_from_connector: Option<DVec2>,
|
pub wire_in_progress_from_connector: Option<DVec2>,
|
||||||
|
wire_in_progress_type: FrontendGraphDataType,
|
||||||
/// The end point of the dragged line (cannot be moved), stored in node graph coordinates
|
/// The end point of the dragged line (cannot be moved), stored in node graph coordinates
|
||||||
pub wire_in_progress_to_connector: Option<DVec2>,
|
pub wire_in_progress_to_connector: Option<DVec2>,
|
||||||
/// State for the context menu popups.
|
/// State for the context menu popups.
|
||||||
@@ -77,15 +79,20 @@ pub struct NodeGraphMessageHandler {
|
|||||||
auto_panning: AutoPanning,
|
auto_panning: AutoPanning,
|
||||||
/// The node to preview on mouse up if alt-clicked
|
/// The node to preview on mouse up if alt-clicked
|
||||||
preview_on_mouse_up: Option<NodeId>,
|
preview_on_mouse_up: Option<NodeId>,
|
||||||
// The index of the import that is being moved
|
/// The index of the import that is being moved
|
||||||
reordering_import: Option<usize>,
|
reordering_import: Option<usize>,
|
||||||
// The index of the export that is being moved
|
/// The index of the export that is being moved
|
||||||
reordering_export: Option<usize>,
|
reordering_export: Option<usize>,
|
||||||
// The end index of the moved port
|
/// The end index of the moved port
|
||||||
end_index: Option<usize>,
|
end_index: Option<usize>,
|
||||||
|
/// Used to keep track of what nodes are sent to the front end so that only visible ones are sent to the frontend
|
||||||
|
frontend_nodes: Vec<NodeId>,
|
||||||
|
/// Used to keep track of what wires are sent to the front end so the old ones can be removed
|
||||||
|
frontend_wires: HashSet<(NodeId, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network.
|
/// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network.
|
||||||
|
#[message_handler_data]
|
||||||
impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGraphMessageHandler {
|
impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGraphMessageHandler {
|
||||||
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, data: NodeGraphHandlerData<'a>) {
|
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, data: NodeGraphHandlerData<'a>) {
|
||||||
let NodeGraphHandlerData {
|
let NodeGraphHandlerData {
|
||||||
@@ -175,7 +182,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
responses.add(PropertiesPanelMessage::Refresh);
|
responses.add(PropertiesPanelMessage::Refresh);
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::CreateNodeFromContextMenu { node_id, node_type, xy } => {
|
NodeGraphMessage::CreateNodeFromContextMenu {
|
||||||
|
node_id,
|
||||||
|
node_type,
|
||||||
|
xy,
|
||||||
|
add_transaction,
|
||||||
|
} => {
|
||||||
let (x, y) = if let Some((x, y)) = xy {
|
let (x, y) = if let Some((x, y)) = xy {
|
||||||
(x, y)
|
(x, y)
|
||||||
} else if let Some(node_graph_ptz) = network_interface.node_graph_ptz(breadcrumb_network_path) {
|
} else if let Some(node_graph_ptz) = network_interface.node_graph_ptz(breadcrumb_network_path) {
|
||||||
@@ -197,7 +209,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
let node_template = document_node_type.default_node_template();
|
let node_template = document_node_type.default_node_template();
|
||||||
self.context_menu = None;
|
self.context_menu = None;
|
||||||
|
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
if add_transaction {
|
||||||
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::InsertNode {
|
responses.add(NodeGraphMessage::InsertNode {
|
||||||
node_id,
|
node_id,
|
||||||
node_template: node_template.clone(),
|
node_template: node_template.clone(),
|
||||||
@@ -220,13 +235,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Ensure connection is to correct input of new node. If it does not have an input then do not connect
|
// Ensure connection is to correct input of new node. If it does not have an input then do not connect
|
||||||
if let Some((input_index, _)) = node_template
|
if let Some((input_index, _)) = node_template.document_node.inputs.iter().enumerate().find(|(_, input)| input.is_exposed()) {
|
||||||
.document_node
|
|
||||||
.inputs
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty()))
|
|
||||||
{
|
|
||||||
responses.add(NodeGraphMessage::CreateWire {
|
responses.add(NodeGraphMessage::CreateWire {
|
||||||
output_connector: *output_connector,
|
output_connector: *output_connector,
|
||||||
input_connector: InputConnector::node(node_id, input_index),
|
input_connector: InputConnector::node(node_id, input_index),
|
||||||
@@ -236,6 +245,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.wire_in_progress_from_connector = None;
|
self.wire_in_progress_from_connector = None;
|
||||||
|
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||||
self.wire_in_progress_to_connector = None;
|
self.wire_in_progress_to_connector = None;
|
||||||
}
|
}
|
||||||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
||||||
@@ -367,9 +377,14 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
responses.add(DocumentMessage::CommitTransaction);
|
responses.add(DocumentMessage::CommitTransaction);
|
||||||
|
|
||||||
// Update the graph UI and re-render
|
// Update the graph UI and re-render
|
||||||
responses.add(PropertiesPanelMessage::Refresh);
|
if graph_view_overlay_open {
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(PropertiesPanelMessage::Refresh);
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
|
} else {
|
||||||
|
responses.add(DocumentMessage::GraphViewOverlay { open: true });
|
||||||
|
responses.add(NavigationMessage::FitViewportToSelection);
|
||||||
|
responses.add(DocumentMessage::ZoomCanvasTo100Percent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
NodeGraphMessage::InsertNode { node_id, node_template } => {
|
NodeGraphMessage::InsertNode { node_id, node_template } => {
|
||||||
network_interface.insert_node(node_id, node_template, selection_network_path);
|
network_interface.insert_node(node_id, node_template, selection_network_path);
|
||||||
@@ -629,6 +644,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
// Abort dragging a wire
|
// Abort dragging a wire
|
||||||
if self.wire_in_progress_from_connector.is_some() {
|
if self.wire_in_progress_from_connector.is_some() {
|
||||||
self.wire_in_progress_from_connector = None;
|
self.wire_in_progress_from_connector = None;
|
||||||
|
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||||
self.wire_in_progress_to_connector = None;
|
self.wire_in_progress_to_connector = None;
|
||||||
responses.add(DocumentMessage::AbortTransaction);
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
||||||
@@ -707,6 +723,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
if self.context_menu.is_some() {
|
if self.context_menu.is_some() {
|
||||||
self.context_menu = None;
|
self.context_menu = None;
|
||||||
self.wire_in_progress_from_connector = None;
|
self.wire_in_progress_from_connector = None;
|
||||||
|
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||||
self.wire_in_progress_to_connector = None;
|
self.wire_in_progress_to_connector = None;
|
||||||
responses.add(FrontendMessage::UpdateContextMenuInformation {
|
responses.add(FrontendMessage::UpdateContextMenuInformation {
|
||||||
context_menu_information: self.context_menu.clone(),
|
context_menu_information: self.context_menu.clone(),
|
||||||
@@ -740,6 +757,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
};
|
};
|
||||||
let Some(output_connector) = output_connector else { return };
|
let Some(output_connector) = output_connector else { return };
|
||||||
self.wire_in_progress_from_connector = network_interface.output_position(&output_connector, selection_network_path);
|
self.wire_in_progress_from_connector = network_interface.output_position(&output_connector, selection_network_path);
|
||||||
|
self.wire_in_progress_type = FrontendGraphDataType::from_type(&network_interface.input_type(clicked_input, breadcrumb_network_path).0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -749,6 +767,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
self.initial_disconnecting = false;
|
self.initial_disconnecting = false;
|
||||||
|
|
||||||
self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path);
|
self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path);
|
||||||
|
if let Some((output_type, source)) = clicked_output
|
||||||
|
.node_id()
|
||||||
|
.map(|node_id| network_interface.output_type(&node_id, clicked_output.index(), breadcrumb_network_path))
|
||||||
|
{
|
||||||
|
self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&output_type, &source);
|
||||||
|
} else {
|
||||||
|
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||||
|
}
|
||||||
|
|
||||||
self.update_node_graph_hints(responses);
|
self.update_node_graph_hints(responses);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -895,9 +922,18 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
let vector_wire = build_vector_wire(
|
||||||
|
wire_in_progress_from_connector,
|
||||||
|
wire_in_progress_to_connector,
|
||||||
|
from_connector_is_layer,
|
||||||
|
to_connector_is_layer,
|
||||||
|
GraphWireStyle::Direct,
|
||||||
|
);
|
||||||
|
let mut path_string = String::new();
|
||||||
|
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
|
||||||
let wire_path = WirePath {
|
let wire_path = WirePath {
|
||||||
path_string: Self::build_wire_path_string(wire_in_progress_from_connector, wire_in_progress_to_connector, from_connector_is_layer, to_connector_is_layer),
|
path_string,
|
||||||
data_type: FrontendGraphDataType::General,
|
data_type: self.wire_in_progress_type,
|
||||||
thick: false,
|
thick: false,
|
||||||
dashed: false,
|
dashed: false,
|
||||||
};
|
};
|
||||||
@@ -941,7 +977,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
self.update_node_graph_hints(responses);
|
self.update_node_graph_hints(responses);
|
||||||
} else if self.reordering_import.is_some() {
|
} else if self.reordering_import.is_some() {
|
||||||
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
|
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
|
||||||
log::error!("Could not get modify import export in PointerUp");
|
log::error!("Could not get modify import export in PointerMove");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// Find the first import that is below the mouse position
|
// Find the first import that is below the mouse position
|
||||||
@@ -961,7 +997,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
responses.add(FrontendMessage::UpdateImportReorderIndex { index: self.end_index });
|
responses.add(FrontendMessage::UpdateImportReorderIndex { index: self.end_index });
|
||||||
} else if self.reordering_export.is_some() {
|
} else if self.reordering_export.is_some() {
|
||||||
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
|
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
|
||||||
log::error!("Could not get modify import export in PointerUp");
|
log::error!("Could not get modify import export in PointerMove");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// Find the first export that is below the mouse position
|
// Find the first export that is below the mouse position
|
||||||
@@ -1043,15 +1079,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
// Get the compatible type from the output connector
|
// Get the compatible type from the output connector
|
||||||
let compatible_type = output_connector.and_then(|output_connector| {
|
let compatible_type = output_connector.and_then(|output_connector| {
|
||||||
output_connector.node_id().and_then(|node_id| {
|
output_connector.node_id().and_then(|node_id| {
|
||||||
let output_index = output_connector.index();
|
|
||||||
// Get the output types from the network interface
|
// Get the output types from the network interface
|
||||||
let output_types = network_interface.output_types(&node_id, selection_network_path);
|
let (output_type, type_source) = network_interface.output_type(&node_id, output_connector.index(), selection_network_path);
|
||||||
|
|
||||||
// Extract the type if available
|
match type_source {
|
||||||
output_types.get(output_index).and_then(|type_option| type_option.as_ref()).map(|(output_type, _)| {
|
TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None,
|
||||||
// Create a search term based on the type
|
_ => Some(format!("type:{}", output_type.nested_type())),
|
||||||
format!("type:{}", output_type.clone().nested_type())
|
}
|
||||||
})
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. };
|
let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. };
|
||||||
@@ -1117,107 +1151,56 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
let has_primary_output_connection = network_interface
|
let has_primary_output_connection = network_interface
|
||||||
.outward_wires(selection_network_path)
|
.outward_wires(selection_network_path)
|
||||||
.is_some_and(|outward_wires| outward_wires.get(&OutputConnector::node(selected_node_id, 0)).is_some_and(|outward_wires| !outward_wires.is_empty()));
|
.is_some_and(|outward_wires| outward_wires.get(&OutputConnector::node(selected_node_id, 0)).is_some_and(|outward_wires| !outward_wires.is_empty()));
|
||||||
let Some(network) = network_interface.nested_network(selection_network_path) else {
|
if !has_primary_output_connection {
|
||||||
return;
|
let Some(network) = network_interface.nested_network(selection_network_path) else {
|
||||||
};
|
return;
|
||||||
if let Some(selected_node) = network.nodes.get(&selected_node_id) {
|
};
|
||||||
// Check if any downstream node has any input that feeds into the primary export of the selected node
|
let Some(selected_node) = network.nodes.get(&selected_node_id) else {
|
||||||
let primary_input_is_value = selected_node.inputs.first().is_some_and(|first_input| first_input.as_value().is_some());
|
return;
|
||||||
// Check that neither the primary input or output of the selected node are already connected.
|
};
|
||||||
if !has_primary_output_connection && primary_input_is_value {
|
// Check that the first visible input is disconnected
|
||||||
|
let selected_node_input_connect_index = selected_node
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|input| input.1.is_exposed())
|
||||||
|
.filter(|input| input.1.as_value().is_some())
|
||||||
|
.map(|input| input.0);
|
||||||
|
if let Some(selected_node_input_connect_index) = selected_node_input_connect_index {
|
||||||
let Some(bounding_box) = network_interface.node_bounding_box(&selected_node_id, selection_network_path) else {
|
let Some(bounding_box) = network_interface.node_bounding_box(&selected_node_id, selection_network_path) else {
|
||||||
log::error!("Could not get bounding box for node: {selected_node_id}");
|
log::error!("Could not get bounding box for node: {selected_node_id}");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// TODO: Cache all wire locations if this is a performance issue
|
let mut wires_to_check = network_interface.node_graph_input_connectors(selection_network_path).into_iter().collect::<HashSet<_>>();
|
||||||
let overlapping_wires = Self::collect_wires(network_interface, selection_network_path)
|
// Prevent inserting on a link that is connected upstream to the selected node
|
||||||
.into_iter()
|
for upstream_node in network_interface.upstream_flow_back_from_nodes(vec![selected_node_id], selection_network_path, network_interface::FlowType::UpstreamFlow) {
|
||||||
.filter(|frontend_wire| {
|
for input_index in 0..network_interface.number_of_inputs(&upstream_node, selection_network_path) {
|
||||||
// Prevent inserting on a link that is connected upstream to the selected node
|
wires_to_check.remove(&InputConnector::node(upstream_node, input_index));
|
||||||
if network_interface
|
}
|
||||||
.upstream_flow_back_from_nodes(vec![selected_node_id], selection_network_path, network_interface::FlowType::UpstreamFlow)
|
}
|
||||||
.any(|upstream_id| {
|
|
||||||
frontend_wire.wire_end.node_id().is_some_and(|wire_end_id| wire_end_id == upstream_id)
|
|
||||||
|| frontend_wire.wire_start.node_id().is_some_and(|wire_start_id| wire_start_id == upstream_id)
|
|
||||||
}) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let overlapping_wires = wires_to_check
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|input| {
|
||||||
// Prevent inserting a layer into a chain
|
// Prevent inserting a layer into a chain
|
||||||
if network_interface.is_layer(&selected_node_id, selection_network_path)
|
if network_interface.is_layer(&selected_node_id, selection_network_path)
|
||||||
&& frontend_wire
|
&& input.node_id().is_some_and(|input_node_id| network_interface.is_chain(&input_node_id, selection_network_path))
|
||||||
.wire_start
|
|
||||||
.node_id()
|
|
||||||
.is_some_and(|wire_start_id| network_interface.is_chain(&wire_start_id, selection_network_path))
|
|
||||||
{
|
{
|
||||||
return false;
|
return None;
|
||||||
}
|
}
|
||||||
|
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
|
||||||
let Some(input_position) = network_interface.input_position(&frontend_wire.wire_end, selection_network_path) else {
|
wire.rectangle_intersections_exist(bounding_box[0], bounding_box[1]).then_some((input, is_stack))
|
||||||
log::error!("Could not get input port position for {:?}", frontend_wire.wire_end);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(output_position) = network_interface.output_position(&frontend_wire.wire_start, selection_network_path) else {
|
|
||||||
log::error!("Could not get output port position for {:?}", frontend_wire.wire_start);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let start_node_is_layer = frontend_wire
|
|
||||||
.wire_end
|
|
||||||
.node_id()
|
|
||||||
.is_some_and(|wire_start_id| network_interface.is_layer(&wire_start_id, selection_network_path));
|
|
||||||
let end_node_is_layer = frontend_wire
|
|
||||||
.wire_end
|
|
||||||
.node_id()
|
|
||||||
.is_some_and(|wire_end_id| network_interface.is_layer(&wire_end_id, selection_network_path));
|
|
||||||
|
|
||||||
let locations = Self::build_wire_path_locations(output_position, input_position, start_node_is_layer, end_node_is_layer);
|
|
||||||
let bezier = bezier_rs::Bezier::from_cubic_dvec2(
|
|
||||||
(locations[0].x, locations[0].y).into(),
|
|
||||||
(locations[1].x, locations[1].y).into(),
|
|
||||||
(locations[2].x, locations[2].y).into(),
|
|
||||||
(locations[3].x, locations[3].y).into(),
|
|
||||||
);
|
|
||||||
|
|
||||||
!bezier.rectangle_intersections(bounding_box[0], bounding_box[1]).is_empty() || bezier.is_contained_within(bounding_box[0], bounding_box[1])
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|mut wire| {
|
|
||||||
if let Some(end_node_id) = wire.wire_end.node_id() {
|
|
||||||
let Some(actual_index_from_exposed) = (0..network_interface.number_of_inputs(&end_node_id, selection_network_path))
|
|
||||||
.filter(|&input_index| {
|
|
||||||
network_interface
|
|
||||||
.input_from_connector(&InputConnector::Node { node_id: end_node_id, input_index }, selection_network_path)
|
|
||||||
.is_some_and(|input| input.is_exposed_to_frontend(selection_network_path.is_empty()))
|
|
||||||
})
|
|
||||||
.nth(wire.wire_end.input_index())
|
|
||||||
else {
|
|
||||||
log::error!("Could not get exposed input index for {:?}", wire.wire_end);
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
wire.wire_end = InputConnector::Node {
|
|
||||||
node_id: end_node_id,
|
|
||||||
input_index: actual_index_from_exposed,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Some(wire)
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let is_stack_wire = |wire: &FrontendNodeWire| match (wire.wire_start.node_id(), wire.wire_end.node_id(), wire.wire_end.input_index()) {
|
|
||||||
(Some(start_id), Some(end_id), input_index) => {
|
|
||||||
input_index == 0 && network_interface.is_layer(&start_id, selection_network_path) && network_interface.is_layer(&end_id, selection_network_path)
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Prioritize vertical thick lines and cancel if there are multiple potential wires
|
// Prioritize vertical thick lines and cancel if there are multiple potential wires
|
||||||
let mut node_wires = Vec::new();
|
let mut node_wires = Vec::new();
|
||||||
let mut stack_wires = Vec::new();
|
let mut stack_wires = Vec::new();
|
||||||
for wire in overlapping_wires {
|
for (overlapping_wire_input, is_stack) in overlapping_wires {
|
||||||
if is_stack_wire(&wire) { stack_wires.push(wire) } else { node_wires.push(wire) }
|
if is_stack {
|
||||||
|
stack_wires.push(overlapping_wire_input)
|
||||||
|
} else {
|
||||||
|
node_wires.push(overlapping_wire_input)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let overlapping_wire = if network_interface.is_layer(&selected_node_id, selection_network_path) {
|
let overlapping_wire = if network_interface.is_layer(&selected_node_id, selection_network_path) {
|
||||||
@@ -1234,29 +1217,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
if let Some(overlapping_wire) = overlapping_wire {
|
if let Some(overlapping_wire) = overlapping_wire {
|
||||||
let Some(network) = network_interface.nested_network(selection_network_path) else {
|
responses.add(NodeGraphMessage::InsertNodeBetween {
|
||||||
return;
|
node_id: selected_node_id,
|
||||||
};
|
input_connector: *overlapping_wire,
|
||||||
// Ensure connection is to first visible input of selected node. If it does not have an input then do not connect
|
insert_node_input_index: selected_node_input_connect_index,
|
||||||
if let Some((selected_node_input_index, _)) = network
|
});
|
||||||
.nodes
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
.get(&selected_node_id)
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
.unwrap()
|
|
||||||
.inputs
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty()))
|
|
||||||
{
|
|
||||||
responses.add(NodeGraphMessage::InsertNodeBetween {
|
|
||||||
node_id: selected_node_id,
|
|
||||||
input_connector: overlapping_wire.wire_end,
|
|
||||||
insert_node_input_index: selected_node_input_index,
|
|
||||||
});
|
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1283,6 +1250,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
self.begin_dragging = false;
|
self.begin_dragging = false;
|
||||||
self.box_selection_start = None;
|
self.box_selection_start = None;
|
||||||
self.wire_in_progress_from_connector = None;
|
self.wire_in_progress_from_connector = None;
|
||||||
|
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||||
self.wire_in_progress_to_connector = None;
|
self.wire_in_progress_to_connector = None;
|
||||||
self.reordering_export = None;
|
self.reordering_export = None;
|
||||||
self.reordering_import = None;
|
self.reordering_import = None;
|
||||||
@@ -1357,23 +1325,52 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)),
|
click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)),
|
||||||
}),
|
}),
|
||||||
NodeGraphMessage::EndSendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { click_targets: None }),
|
NodeGraphMessage::EndSendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { click_targets: None }),
|
||||||
|
NodeGraphMessage::UnloadWires => {
|
||||||
|
for input in network_interface.node_graph_input_connectors(breadcrumb_network_path) {
|
||||||
|
network_interface.unload_wire(&input, breadcrumb_network_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(FrontendMessage::ClearAllNodeGraphWires);
|
||||||
|
}
|
||||||
|
NodeGraphMessage::SendWires => {
|
||||||
|
let wires = self.collect_wires(network_interface, preferences.graph_wire_style, breadcrumb_network_path);
|
||||||
|
responses.add(FrontendMessage::UpdateNodeGraphWires { wires });
|
||||||
|
}
|
||||||
|
NodeGraphMessage::UpdateVisibleNodes => {
|
||||||
|
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let viewport_bbox = ipp.document_bounds();
|
||||||
|
let document_bbox: [DVec2; 2] = viewport_bbox.map(|p| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(p));
|
||||||
|
|
||||||
|
let mut nodes = Vec::new();
|
||||||
|
for node_id in &self.frontend_nodes {
|
||||||
|
let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else {
|
||||||
|
log::error!("Could not get bbox for node: {:?}", node_id);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if node_bbox[1].x >= document_bbox[0].x && node_bbox[0].x <= document_bbox[1].x && node_bbox[1].y >= document_bbox[0].y && node_bbox[0].y <= document_bbox[1].y {
|
||||||
|
nodes.push(*node_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(FrontendMessage::UpdateVisibleNodes { nodes });
|
||||||
|
}
|
||||||
NodeGraphMessage::SendGraph => {
|
NodeGraphMessage::SendGraph => {
|
||||||
responses.add(NodeGraphMessage::UpdateLayerPanel);
|
responses.add(NodeGraphMessage::UpdateLayerPanel);
|
||||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||||
responses.add(PropertiesPanelMessage::Refresh);
|
responses.add(PropertiesPanelMessage::Refresh);
|
||||||
if breadcrumb_network_path == selection_network_path && graph_view_overlay_open {
|
if breadcrumb_network_path == selection_network_path && graph_view_overlay_open {
|
||||||
// TODO: Implement culling of nodes and wires whose bounding boxes are outside of the viewport
|
|
||||||
let wires = Self::collect_wires(network_interface, breadcrumb_network_path);
|
|
||||||
let nodes = self.collect_nodes(network_interface, breadcrumb_network_path);
|
let nodes = self.collect_nodes(network_interface, breadcrumb_network_path);
|
||||||
|
self.frontend_nodes = nodes.iter().map(|node| node.id).collect();
|
||||||
|
responses.add(FrontendMessage::UpdateNodeGraphNodes { nodes });
|
||||||
|
responses.add(NodeGraphMessage::UpdateVisibleNodes);
|
||||||
|
|
||||||
let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path);
|
let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path);
|
||||||
let wires_direct_not_grid_aligned = preferences.graph_wire_style.is_direct();
|
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::UpdateImportsExports);
|
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||||
responses.add(FrontendMessage::UpdateNodeGraph {
|
|
||||||
nodes,
|
|
||||||
wires,
|
|
||||||
wires_direct_not_grid_aligned,
|
|
||||||
});
|
|
||||||
responses.add(FrontendMessage::UpdateLayerWidths {
|
responses.add(FrontendMessage::UpdateLayerWidths {
|
||||||
layer_widths,
|
layer_widths,
|
||||||
chain_widths,
|
chain_widths,
|
||||||
@@ -1455,6 +1452,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
Ordering::Equal => {}
|
Ordering::Equal => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::SendWires);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::ToggleSelectedAsLayersOrNodes => {
|
NodeGraphMessage::ToggleSelectedAsLayersOrNodes => {
|
||||||
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
|
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
|
||||||
@@ -1474,6 +1473,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
}
|
}
|
||||||
NodeGraphMessage::ShiftNodePosition { node_id, x, y } => {
|
NodeGraphMessage::ShiftNodePosition { node_id, x, y } => {
|
||||||
network_interface.shift_absolute_node_position(&node_id, IVec2::new(x, y), selection_network_path);
|
network_interface.shift_absolute_node_position(&node_id, IVec2::new(x, y), selection_network_path);
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::SendWires);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => {
|
NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => {
|
||||||
if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) {
|
if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) {
|
||||||
@@ -1487,6 +1488,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
});
|
});
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
|
responses.add(NodeGraphMessage::SendWires);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SetDisplayName {
|
NodeGraphMessage::SetDisplayName {
|
||||||
node_id,
|
node_id,
|
||||||
@@ -1623,7 +1625,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
|
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
|
||||||
log::error!("Could not get network metadata in PointerMove");
|
log::error!("Could not get network metadata in UpdateBoxSelection");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1689,7 +1691,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next();
|
.next();
|
||||||
|
responses.add(NodeGraphMessage::UpdateVisibleNodes);
|
||||||
|
responses.add(NodeGraphMessage::SendWires);
|
||||||
responses.add(FrontendMessage::UpdateImportsExports {
|
responses.add(FrontendMessage::UpdateImportsExports {
|
||||||
imports,
|
imports,
|
||||||
exports,
|
exports,
|
||||||
@@ -1835,6 +1838,7 @@ impl NodeGraphMessageHandler {
|
|||||||
node_id: Some(node_id),
|
node_id: Some(node_id),
|
||||||
node_type: node_type.clone(),
|
node_type: node_type.clone(),
|
||||||
xy: None,
|
xy: None,
|
||||||
|
add_transaction: true,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(),
|
NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(),
|
||||||
@@ -2151,69 +2155,39 @@ impl NodeGraphMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_wires(network_interface: &NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNodeWire> {
|
fn collect_wires(&mut self, network_interface: &mut NodeNetworkInterface, graph_wire_style: GraphWireStyle, breadcrumb_network_path: &[NodeId]) -> Vec<WirePathUpdate> {
|
||||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
let mut added_wires = network_interface
|
||||||
log::error!("Could not get network when collecting wires");
|
.node_graph_input_connectors(breadcrumb_network_path)
|
||||||
return Vec::new();
|
|
||||||
};
|
|
||||||
let mut wires = network
|
|
||||||
.nodes
|
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|(wire_end, node)| node.inputs.iter().filter(|input| input.is_exposed()).enumerate().map(move |(index, input)| (input, wire_end, index)))
|
.filter_map(|connector| network_interface.newly_loaded_input_wire(connector, graph_wire_style, breadcrumb_network_path))
|
||||||
.filter_map(|(input, &wire_end, wire_end_input_index)| {
|
|
||||||
match *input {
|
|
||||||
NodeInput::Node {
|
|
||||||
node_id: wire_start,
|
|
||||||
output_index: wire_start_output_index,
|
|
||||||
// TODO: add ui for lambdas
|
|
||||||
lambda: _,
|
|
||||||
} => Some(FrontendNodeWire {
|
|
||||||
wire_start: OutputConnector::node(wire_start, wire_start_output_index),
|
|
||||||
wire_end: InputConnector::node(wire_end, wire_end_input_index),
|
|
||||||
dashed: false,
|
|
||||||
}),
|
|
||||||
NodeInput::Network { import_index, .. } => Some(FrontendNodeWire {
|
|
||||||
wire_start: OutputConnector::Import(import_index),
|
|
||||||
wire_end: InputConnector::node(wire_end, wire_end_input_index),
|
|
||||||
dashed: false,
|
|
||||||
}),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Connect primary export to root node, since previewing a node will change the primary export
|
let changed_wire_inputs = added_wires.iter().map(|update| (update.id, update.input_index)).collect::<Vec<_>>();
|
||||||
if let Some(root_node) = network_interface.root_node(breadcrumb_network_path) {
|
self.frontend_wires.extend(changed_wire_inputs);
|
||||||
wires.push(FrontendNodeWire {
|
|
||||||
wire_start: OutputConnector::node(root_node.node_id, root_node.output_index),
|
let mut orphaned_wire_inputs = self.frontend_wires.clone();
|
||||||
wire_end: InputConnector::Export(0),
|
self.frontend_wires = network_interface
|
||||||
dashed: false,
|
.node_graph_wire_inputs(breadcrumb_network_path)
|
||||||
});
|
.iter()
|
||||||
|
.filter_map(|visible_wire_input| orphaned_wire_inputs.take(visible_wire_input))
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
added_wires.extend(orphaned_wire_inputs.into_iter().map(|(id, input_index)| WirePathUpdate {
|
||||||
|
id,
|
||||||
|
input_index,
|
||||||
|
wire_path_update: None,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if let Some(wire_to_root) = network_interface.wire_to_root(graph_wire_style, breadcrumb_network_path) {
|
||||||
|
added_wires.push(wire_to_root);
|
||||||
|
} else {
|
||||||
|
added_wires.push(WirePathUpdate {
|
||||||
|
id: NodeId(u64::MAX),
|
||||||
|
input_index: usize::MAX,
|
||||||
|
wire_path_update: None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect rest of exports to their actual export field since they are not affected by previewing. Only connect the primary export if it is dashed
|
added_wires
|
||||||
for (i, export) in network.exports.iter().enumerate() {
|
|
||||||
let dashed = matches!(network_interface.previewing(breadcrumb_network_path), Previewing::Yes { .. }) && i == 0;
|
|
||||||
if dashed || i != 0 {
|
|
||||||
if let NodeInput::Node { node_id, output_index, .. } = export {
|
|
||||||
wires.push(FrontendNodeWire {
|
|
||||||
wire_start: OutputConnector::Node {
|
|
||||||
node_id: *node_id,
|
|
||||||
output_index: *output_index,
|
|
||||||
},
|
|
||||||
wire_end: InputConnector::Export(i),
|
|
||||||
dashed,
|
|
||||||
});
|
|
||||||
} else if let NodeInput::Network { import_index, .. } = *export {
|
|
||||||
wires.push(FrontendNodeWire {
|
|
||||||
wire_start: OutputConnector::Import(import_index),
|
|
||||||
wire_end: InputConnector::Export(i),
|
|
||||||
dashed,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wires
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNode> {
|
fn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNode> {
|
||||||
@@ -2237,6 +2211,7 @@ impl NodeGraphMessageHandler {
|
|||||||
log::error!("Could not get position for node {node_id}");
|
log::error!("Could not get position for node {node_id}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut frontend_inputs_lookup = frontend_inputs_lookup(breadcrumb_network_path, network_interface);
|
let mut frontend_inputs_lookup = frontend_inputs_lookup(breadcrumb_network_path, network_interface);
|
||||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
||||||
log::error!("Could not get nested network when collecting nodes");
|
log::error!("Could not get nested network when collecting nodes");
|
||||||
@@ -2252,13 +2227,14 @@ impl NodeGraphMessageHandler {
|
|||||||
let node_id_path = [breadcrumb_network_path, (&[node_id])].concat();
|
let node_id_path = [breadcrumb_network_path, (&[node_id])].concat();
|
||||||
|
|
||||||
let inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default();
|
let inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default();
|
||||||
|
|
||||||
let mut inputs = inputs.into_iter().map(|input| {
|
let mut inputs = inputs.into_iter().map(|input| {
|
||||||
input.map(|input| FrontendGraphInput {
|
input.map(|input| FrontendGraphInput {
|
||||||
data_type: FrontendGraphDataType::displayed_type(&input.ty, &input.type_source),
|
data_type: FrontendGraphDataType::displayed_type(&input.ty, &input.type_source),
|
||||||
resolved_type: Some(format!("{:?}", &input.ty)),
|
resolved_type: format!("{:?}", &input.ty),
|
||||||
valid_types: input.valid_types.iter().map(|ty| ty.to_string()).collect(),
|
valid_types: input.valid_types.iter().map(|ty| ty.to_string()).collect(),
|
||||||
name: input.input_name.unwrap_or_else(|| input.ty.nested_type().to_string()),
|
name: input.input_name,
|
||||||
description: input.input_description.unwrap_or_default(),
|
description: input.input_description,
|
||||||
connected_to: input.output_connector,
|
connected_to: input.output_connector,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -2266,20 +2242,16 @@ impl NodeGraphMessageHandler {
|
|||||||
let primary_input = inputs.next().flatten();
|
let primary_input = inputs.next().flatten();
|
||||||
let exposed_inputs = inputs.flatten().collect();
|
let exposed_inputs = inputs.flatten().collect();
|
||||||
|
|
||||||
let output_types = network_interface.output_types(&node_id, breadcrumb_network_path);
|
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
|
||||||
let primary_output_type = output_types.first().cloned().flatten();
|
let frontend_data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
|
||||||
let frontend_data_type = if let Some((output_type, type_source)) = &primary_output_type {
|
|
||||||
FrontendGraphDataType::displayed_type(output_type, type_source)
|
|
||||||
} else {
|
|
||||||
FrontendGraphDataType::General
|
|
||||||
};
|
|
||||||
let connected_to = outward_wires.get(&OutputConnector::node(node_id, 0)).cloned().unwrap_or_default();
|
let connected_to = outward_wires.get(&OutputConnector::node(node_id, 0)).cloned().unwrap_or_default();
|
||||||
let primary_output = if network_interface.has_primary_output(&node_id, breadcrumb_network_path) && !output_types.is_empty() {
|
let primary_output = if network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
||||||
Some(FrontendGraphOutput {
|
Some(FrontendGraphOutput {
|
||||||
data_type: frontend_data_type,
|
data_type: frontend_data_type,
|
||||||
name: "Output 1".to_string(),
|
name: "Output 1".to_string(),
|
||||||
description: String::new(),
|
description: String::new(),
|
||||||
resolved_type: primary_output_type.map(|(input, _)| format!("{input:?}")),
|
resolved_type: format!("{:?}", output_type),
|
||||||
connected_to,
|
connected_to,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -2287,15 +2259,13 @@ impl NodeGraphMessageHandler {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut exposed_outputs = Vec::new();
|
let mut exposed_outputs = Vec::new();
|
||||||
for (index, exposed_output) in output_types.iter().enumerate() {
|
for output_index in 0..network_interface.number_of_outputs(&node_id, breadcrumb_network_path) {
|
||||||
if index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
if output_index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let frontend_data_type = if let Some((output_type, type_source)) = &exposed_output {
|
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
|
||||||
FrontendGraphDataType::displayed_type(output_type, type_source)
|
let data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
|
||||||
} else {
|
|
||||||
FrontendGraphDataType::General
|
|
||||||
};
|
|
||||||
let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(&node_id) else {
|
let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(&node_id) else {
|
||||||
log::error!("Could not get node_metadata when getting output for {node_id}");
|
log::error!("Could not get node_metadata when getting output for {node_id}");
|
||||||
continue;
|
continue;
|
||||||
@@ -2303,17 +2273,17 @@ impl NodeGraphMessageHandler {
|
|||||||
let output_name = node_metadata
|
let output_name = node_metadata
|
||||||
.persistent_metadata
|
.persistent_metadata
|
||||||
.output_names
|
.output_names
|
||||||
.get(index)
|
.get(output_index)
|
||||||
.map(|output_name| output_name.to_string())
|
.cloned()
|
||||||
.filter(|output_name| !output_name.is_empty())
|
.filter(|output_name| !output_name.is_empty())
|
||||||
.unwrap_or_else(|| exposed_output.clone().map(|(output_type, _)| output_type.nested_type().to_string()).unwrap_or_default());
|
.unwrap_or_else(|| output_type.nested_type().to_string());
|
||||||
|
|
||||||
let connected_to = outward_wires.get(&OutputConnector::node(node_id, index)).cloned().unwrap_or_default();
|
let connected_to = outward_wires.get(&OutputConnector::node(node_id, output_index)).cloned().unwrap_or_default();
|
||||||
exposed_outputs.push(FrontendGraphOutput {
|
exposed_outputs.push(FrontendGraphOutput {
|
||||||
data_type: frontend_data_type,
|
data_type,
|
||||||
name: output_name,
|
name: output_name,
|
||||||
description: String::new(),
|
description: String::new(),
|
||||||
resolved_type: exposed_output.clone().map(|(input, _)| format!("{input:?}")),
|
resolved_type: format!("{:?}", output_type),
|
||||||
connected_to,
|
connected_to,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2416,9 +2386,9 @@ impl NodeGraphMessageHandler {
|
|||||||
network_interface.upstream_flow_back_from_nodes(vec![node_id], &[], network_interface::FlowType::HorizontalFlow).last().is_some_and(|node_id|
|
network_interface.upstream_flow_back_from_nodes(vec![node_id], &[], network_interface::FlowType::HorizontalFlow).last().is_some_and(|node_id|
|
||||||
network_interface.document_node(&node_id, &[]).map_or_else(||{log::error!("Could not get node {node_id} in update_layer_panel"); false}, |node| {
|
network_interface.document_node(&node_id, &[]).map_or_else(||{log::error!("Could not get node {node_id} in update_layer_panel"); false}, |node| {
|
||||||
if network_interface.is_layer(&node_id, &[]) {
|
if network_interface.is_layer(&node_id, &[]) {
|
||||||
node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(1).is_some_and(|input| input.as_value().is_some())
|
node.inputs.iter().filter(|input| input.is_exposed()).nth(1).is_some_and(|input| input.as_value().is_some())
|
||||||
} else {
|
} else {
|
||||||
node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(0).is_some_and(|input| input.as_value().is_some())
|
node.inputs.iter().filter(|input| input.is_exposed()).nth(0).is_some_and(|input| input.as_value().is_some())
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
@@ -2467,66 +2437,6 @@ impl NodeGraphMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_wire_path_string(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> String {
|
|
||||||
let locations = Self::build_wire_path_locations(output_position, input_position, vertical_out, vertical_in);
|
|
||||||
let smoothing = 0.5;
|
|
||||||
let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing);
|
|
||||||
let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing);
|
|
||||||
format!(
|
|
||||||
"M{},{} L{},{} C{},{} {},{} {},{} L{},{}",
|
|
||||||
locations[0].x,
|
|
||||||
locations[0].y,
|
|
||||||
locations[1].x,
|
|
||||||
locations[1].y,
|
|
||||||
locations[1].x + delta01.x,
|
|
||||||
locations[1].y + delta01.y,
|
|
||||||
locations[2].x - delta23.x,
|
|
||||||
locations[2].y - delta23.y,
|
|
||||||
locations[2].x,
|
|
||||||
locations[2].y,
|
|
||||||
locations[3].x,
|
|
||||||
locations[3].y
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_wire_path_locations(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<DVec2> {
|
|
||||||
let horizontal_gap = (output_position.x - input_position.x).abs();
|
|
||||||
let vertical_gap = (output_position.y - input_position.y).abs();
|
|
||||||
// TODO: Finish this commented out code replacement for the code below it based on this diagram: <https://files.keavon.com/-/SuperbWideFoxterrier/capture.png>
|
|
||||||
// // Straight: stacking lines which are always straight, or a straight horizontal wire between two aligned nodes
|
|
||||||
// if ((verticalOut && vertical_in) || (!verticalOut && !vertical_in && vertical_gap === 0)) {
|
|
||||||
// return [
|
|
||||||
// { x: output_position.x, y: output_position.y },
|
|
||||||
// { x: input_position.x, y: input_position.y },
|
|
||||||
// ];
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // L-shape bend
|
|
||||||
// if (verticalOut !== vertical_in) {
|
|
||||||
// }
|
|
||||||
|
|
||||||
let curve_length = 24.;
|
|
||||||
let curve_falloff_rate = curve_length * std::f64::consts::PI * 2.;
|
|
||||||
|
|
||||||
let horizontal_curve_amount = -(2_f64.powf((-10. * horizontal_gap) / curve_falloff_rate)) + 1.;
|
|
||||||
let vertical_curve_amount = -(2_f64.powf((-10. * vertical_gap) / curve_falloff_rate)) + 1.;
|
|
||||||
let horizontal_curve = horizontal_curve_amount * curve_length;
|
|
||||||
let vertical_curve = vertical_curve_amount * curve_length;
|
|
||||||
|
|
||||||
vec![
|
|
||||||
output_position,
|
|
||||||
DVec2::new(
|
|
||||||
if vertical_out { output_position.x } else { output_position.x + horizontal_curve },
|
|
||||||
if vertical_out { output_position.y - vertical_curve } else { output_position.y },
|
|
||||||
),
|
|
||||||
DVec2::new(
|
|
||||||
if vertical_in { input_position.x } else { input_position.x - horizontal_curve },
|
|
||||||
if vertical_in { input_position.y + vertical_curve } else { input_position.y },
|
|
||||||
),
|
|
||||||
DVec2::new(input_position.x, input_position.y),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_node_graph_hints(&self, responses: &mut VecDeque<Message>) {
|
pub fn update_node_graph_hints(&self, responses: &mut VecDeque<Message>) {
|
||||||
// A wire is in progress and its start and end connectors are set
|
// A wire is in progress and its start and end connectors are set
|
||||||
let wiring = self.wire_in_progress_from_connector.is_some();
|
let wiring = self.wire_in_progress_from_connector.is_some();
|
||||||
@@ -2570,8 +2480,8 @@ impl NodeGraphMessageHandler {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct InputLookup {
|
struct InputLookup {
|
||||||
input_name: Option<String>,
|
input_name: String,
|
||||||
input_description: Option<String>,
|
input_description: String,
|
||||||
ty: Type,
|
ty: Type,
|
||||||
type_source: TypeSource,
|
type_source: TypeSource,
|
||||||
valid_types: Vec<Type>,
|
valid_types: Vec<Type>,
|
||||||
@@ -2586,34 +2496,31 @@ fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface:
|
|||||||
return Default::default();
|
return Default::default();
|
||||||
};
|
};
|
||||||
let mut frontend_inputs_lookup = HashMap::new();
|
let mut frontend_inputs_lookup = HashMap::new();
|
||||||
for (&node_id, node) in network.nodes.iter() {
|
for (node_id, index, output_connector, is_exposed) in network
|
||||||
let mut inputs = Vec::with_capacity(node.inputs.len());
|
.nodes
|
||||||
for (index, input) in node.inputs.iter().enumerate() {
|
.iter()
|
||||||
let is_exposed = input.is_exposed_to_frontend(breadcrumb_network_path.is_empty());
|
.flat_map(|(node_id, node)| {
|
||||||
|
node.inputs
|
||||||
// Skip not exposed inputs (they still get an entry to help with finding the primary input)
|
.iter()
|
||||||
if !is_exposed {
|
.enumerate()
|
||||||
inputs.push(None);
|
.map(|(index, input)| (*node_id, index, OutputConnector::from_input(input), input.is_exposed()))
|
||||||
continue;
|
})
|
||||||
}
|
.collect::<Vec<_>>()
|
||||||
|
{
|
||||||
|
// Skip not exposed inputs (they still get an entry to help with finding the primary input)
|
||||||
|
let lookup = if !is_exposed {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
// Get the name from the metadata here (since it also requires a reference to the `network_interface`)
|
// Get the name from the metadata here (since it also requires a reference to the `network_interface`)
|
||||||
let input_name = network_interface
|
let (input_name, input_description) = network_interface.displayed_input_name_and_description(&node_id, index, breadcrumb_network_path);
|
||||||
.input_name(node_id, index, breadcrumb_network_path)
|
Some(InputLookup {
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
.map(|name| name.to_string());
|
|
||||||
let input_description = network_interface.input_description(node_id, index, breadcrumb_network_path).map(|description| description.to_string());
|
|
||||||
// Get the output connector that feeds into this input (done here as well for simplicity)
|
|
||||||
let connector = OutputConnector::from_input(input);
|
|
||||||
|
|
||||||
inputs.push(Some(InputLookup {
|
|
||||||
input_name,
|
input_name,
|
||||||
input_description,
|
input_description,
|
||||||
output_connector: connector,
|
output_connector,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}));
|
})
|
||||||
}
|
};
|
||||||
frontend_inputs_lookup.insert(node_id, inputs);
|
frontend_inputs_lookup.entry(node_id).or_insert_with(Vec::new).push(lookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (&node_id, value) in frontend_inputs_lookup.iter_mut() {
|
for (&node_id, value) in frontend_inputs_lookup.iter_mut() {
|
||||||
@@ -2656,6 +2563,7 @@ impl Default for NodeGraphMessageHandler {
|
|||||||
select_if_not_dragged: None,
|
select_if_not_dragged: None,
|
||||||
wire_in_progress_from_connector: None,
|
wire_in_progress_from_connector: None,
|
||||||
wire_in_progress_to_connector: None,
|
wire_in_progress_to_connector: None,
|
||||||
|
wire_in_progress_type: FrontendGraphDataType::General,
|
||||||
context_menu: None,
|
context_menu: None,
|
||||||
deselect_on_pointer_up: None,
|
deselect_on_pointer_up: None,
|
||||||
auto_panning: Default::default(),
|
auto_panning: Default::default(),
|
||||||
@@ -2663,6 +2571,8 @@ impl Default for NodeGraphMessageHandler {
|
|||||||
reordering_export: None,
|
reordering_export: None,
|
||||||
reordering_import: None,
|
reordering_import: None,
|
||||||
end_index: None,
|
end_index: None,
|
||||||
|
frontend_nodes: Vec::new(),
|
||||||
|
frontend_wires: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,17 +60,12 @@ pub fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphData
|
|||||||
"Expose this parameter as a node input in the graph"
|
"Expose this parameter as a node input in the graph"
|
||||||
})
|
})
|
||||||
.on_update(move |_parameter| {
|
.on_update(move |_parameter| {
|
||||||
Message::Batched(Box::new([
|
Message::Batched(Box::new([NodeGraphMessage::ExposeInput {
|
||||||
NodeGraphMessage::ExposeInput {
|
input_connector: InputConnector::node(node_id, index),
|
||||||
input_connector: InputConnector::node(node_id, index),
|
set_to_exposed: !exposed,
|
||||||
set_to_exposed: !exposed,
|
start_transaction: true,
|
||||||
start_transaction: true,
|
}
|
||||||
}
|
.into()]))
|
||||||
.into(),
|
|
||||||
DocumentMessage::GraphViewOverlay { open: true }.into(),
|
|
||||||
NavigationMessage::FitViewportToSelection.into(),
|
|
||||||
DocumentMessage::ZoomCanvasTo100Percent.into(),
|
|
||||||
]))
|
|
||||||
})
|
})
|
||||||
.widget_holder()
|
.widget_holder()
|
||||||
}
|
}
|
||||||
@@ -85,28 +80,31 @@ pub fn add_blank_assist(widgets: &mut Vec<WidgetHolder>) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo, data_type: FrontendGraphDataType) -> Vec<WidgetHolder> {
|
pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||||
start_widgets_exposable(parameter_widgets_info, data_type, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_widgets_exposable(parameter_widgets_info: ParameterWidgetsInfo, data_type: FrontendGraphDataType, exposable: bool) -> Vec<WidgetHolder> {
|
|
||||||
let ParameterWidgetsInfo {
|
let ParameterWidgetsInfo {
|
||||||
document_node,
|
document_node,
|
||||||
node_id,
|
node_id,
|
||||||
index,
|
index,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
|
input_type,
|
||||||
blank_assist,
|
blank_assist,
|
||||||
|
exposeable,
|
||||||
} = parameter_widgets_info;
|
} = parameter_widgets_info;
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else {
|
||||||
|
log::warn!("A widget failed to be built because its document node is invalid.");
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return vec![];
|
return vec![];
|
||||||
};
|
};
|
||||||
let description = if description != "TODO" { description } else { "" };
|
let description = if description != "TODO" { description } else { String::new() };
|
||||||
let mut widgets = Vec::with_capacity(6);
|
let mut widgets = Vec::with_capacity(6);
|
||||||
if exposable {
|
if exposeable {
|
||||||
widgets.push(expose_widget(node_id, index, data_type, input.is_exposed()));
|
widgets.push(expose_widget(node_id, index, input_type, input.is_exposed()));
|
||||||
}
|
}
|
||||||
widgets.push(TextLabel::new(name).tooltip(description).widget_holder());
|
widgets.push(TextLabel::new(name).tooltip(description).widget_holder());
|
||||||
if blank_assist {
|
if blank_assist {
|
||||||
@@ -126,18 +124,6 @@ pub(crate) fn property_from_type(
|
|||||||
step: Option<f64>,
|
step: Option<f64>,
|
||||||
context: &mut NodePropertiesContext,
|
context: &mut NodePropertiesContext,
|
||||||
) -> Result<Vec<LayoutGroup>, Vec<LayoutGroup>> {
|
) -> Result<Vec<LayoutGroup>, Vec<LayoutGroup>> {
|
||||||
let Some(network) = context.network_interface.nested_network(context.selection_network_path) else {
|
|
||||||
log::warn!("A widget failed to be built for node {node_id}, index {index} because the network could not be determined");
|
|
||||||
return Err(vec![]);
|
|
||||||
};
|
|
||||||
let Some(document_node) = network.nodes.get(&node_id) else {
|
|
||||||
log::warn!("A widget failed to be built for node {node_id}, index {index} because the document node does not exist");
|
|
||||||
return Err(vec![]);
|
|
||||||
};
|
|
||||||
|
|
||||||
let name = context.network_interface.input_name(node_id, index, context.selection_network_path).unwrap_or_default();
|
|
||||||
let description = context.network_interface.input_description(node_id, index, context.selection_network_path).unwrap_or_default();
|
|
||||||
|
|
||||||
let (mut number_min, mut number_max, range) = number_options;
|
let (mut number_min, mut number_max, range) = number_options;
|
||||||
let mut number_input = NumberInput::default();
|
let mut number_input = NumberInput::default();
|
||||||
if let Some((range_start, range_end)) = range {
|
if let Some((range_start, range_end)) = range {
|
||||||
@@ -158,7 +144,7 @@ pub(crate) fn property_from_type(
|
|||||||
let min = |x: f64| number_min.unwrap_or(x);
|
let min = |x: f64| number_min.unwrap_or(x);
|
||||||
let max = |x: f64| number_max.unwrap_or(x);
|
let max = |x: f64| number_max.unwrap_or(x);
|
||||||
|
|
||||||
let default_info = ParameterWidgetsInfo::new(document_node, node_id, index, name, description, true);
|
let default_info = ParameterWidgetsInfo::new(node_id, index, true, context);
|
||||||
|
|
||||||
let mut extra_widgets = vec![];
|
let mut extra_widgets = vec![];
|
||||||
let widgets = match ty {
|
let widgets = match ty {
|
||||||
@@ -176,6 +162,7 @@ pub(crate) fn property_from_type(
|
|||||||
Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
||||||
Some("Resolution") => coordinate_widget(default_info, "W", "H", unit.unwrap_or(" px"), Some(64.)),
|
Some("Resolution") => coordinate_widget(default_info, "W", "H", unit.unwrap_or(" px"), Some(64.)),
|
||||||
Some("PixelSize") => coordinate_widget(default_info, "X", "Y", unit.unwrap_or(" px"), None),
|
Some("PixelSize") => coordinate_widget(default_info, "X", "Y", unit.unwrap_or(" px"), None),
|
||||||
|
Some("TextArea") => text_area_widget(default_info).into(),
|
||||||
|
|
||||||
// For all other types, use TypeId-based matching
|
// For all other types, use TypeId-based matching
|
||||||
_ => {
|
_ => {
|
||||||
@@ -247,7 +234,7 @@ pub(crate) fn property_from_type(
|
|||||||
// OTHER
|
// OTHER
|
||||||
// =====
|
// =====
|
||||||
_ => {
|
_ => {
|
||||||
let mut widgets = start_widgets(default_info, FrontendGraphDataType::General);
|
let mut widgets = start_widgets(default_info);
|
||||||
widgets.extend_from_slice(&[
|
widgets.extend_from_slice(&[
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
TextLabel::new("-")
|
TextLabel::new("-")
|
||||||
@@ -277,8 +264,9 @@ pub(crate) fn property_from_type(
|
|||||||
pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return Vec::new() };
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return vec![];
|
return vec![];
|
||||||
@@ -298,8 +286,9 @@ pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHo
|
|||||||
pub fn text_area_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
pub fn text_area_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return Vec::new() };
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return vec![];
|
return vec![];
|
||||||
@@ -319,8 +308,9 @@ pub fn text_area_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<Wid
|
|||||||
pub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input: CheckboxInput) -> Vec<WidgetHolder> {
|
pub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input: CheckboxInput) -> Vec<WidgetHolder> {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return Vec::new() };
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return vec![];
|
return vec![];
|
||||||
@@ -341,8 +331,9 @@ pub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input:
|
|||||||
pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disabled: bool) -> Vec<WidgetHolder> {
|
pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disabled: bool) -> Vec<WidgetHolder> {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return Vec::new() };
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return vec![];
|
return vec![];
|
||||||
@@ -371,7 +362,7 @@ pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disa
|
|||||||
pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widgets: &mut Vec<LayoutGroup>) -> LayoutGroup {
|
pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widgets: &mut Vec<LayoutGroup>) -> LayoutGroup {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut location_widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
let mut location_widgets = start_widgets(parameter_widgets_info);
|
||||||
location_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
location_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
|
||||||
let mut scale_widgets = vec![TextLabel::new("").widget_holder()];
|
let mut scale_widgets = vec![TextLabel::new("").widget_holder()];
|
||||||
@@ -382,10 +373,12 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
|||||||
add_blank_assist(&mut resolution_widgets);
|
add_blank_assist(&mut resolution_widgets);
|
||||||
resolution_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
resolution_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return Vec::new().into();
|
return Vec::new().into();
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(&TaggedValue::Footprint(footprint)) = input.as_non_exposed_value() {
|
if let Some(&TaggedValue::Footprint(footprint)) = input.as_non_exposed_value() {
|
||||||
let top_left = footprint.transform.transform_point2(DVec2::ZERO);
|
let top_left = footprint.transform.transform_point2(DVec2::ZERO);
|
||||||
let bounds = footprint.scale();
|
let bounds = footprint.scale();
|
||||||
@@ -517,8 +510,9 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
|||||||
pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>) -> LayoutGroup {
|
pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>) -> LayoutGroup {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return LayoutGroup::Row { widgets: vec![] };
|
return LayoutGroup::Row { widgets: vec![] };
|
||||||
@@ -629,7 +623,7 @@ pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str,
|
|||||||
pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text_input: TextInput) -> Vec<WidgetHolder> {
|
pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text_input: TextInput) -> Vec<WidgetHolder> {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
|
||||||
let from_string = |string: &str| {
|
let from_string = |string: &str| {
|
||||||
string
|
string
|
||||||
@@ -641,6 +635,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
|
|||||||
.map(TaggedValue::VecF64)
|
.map(TaggedValue::VecF64)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return Vec::new() };
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return vec![];
|
return vec![];
|
||||||
@@ -660,7 +655,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
|
|||||||
pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
|
pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
|
||||||
let from_string = |string: &str| {
|
let from_string = |string: &str| {
|
||||||
string
|
string
|
||||||
@@ -672,6 +667,7 @@ pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo,
|
|||||||
.map(TaggedValue::VecDVec2)
|
.map(TaggedValue::VecDVec2)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return Vec::new() };
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return vec![];
|
return vec![];
|
||||||
@@ -691,11 +687,12 @@ pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo,
|
|||||||
pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetHolder>, Option<Vec<WidgetHolder>>) {
|
pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetHolder>, Option<Vec<WidgetHolder>>) {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut first_widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
let mut first_widgets = start_widgets(parameter_widgets_info);
|
||||||
let mut second_widgets = None;
|
let mut second_widgets = None;
|
||||||
|
|
||||||
let from_font_input = |font: &FontInput| TaggedValue::Font(Font::new(font.font_family.clone(), font.font_style.clone()));
|
let from_font_input = |font: &FontInput| TaggedValue::Font(Font::new(font.font_family.clone(), font.font_style.clone()));
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return (Vec::new(), None) };
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return (vec![], None);
|
return (vec![], None);
|
||||||
@@ -725,7 +722,7 @@ pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetH
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::VectorData);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
|
||||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
widgets.push(TextLabel::new("Vector data is supplied through the node graph").widget_holder());
|
widgets.push(TextLabel::new("Vector data is supplied through the node graph").widget_holder());
|
||||||
@@ -734,7 +731,7 @@ pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<W
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Raster);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
|
||||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
widgets.push(TextLabel::new("Raster data is supplied through the node graph").widget_holder());
|
widgets.push(TextLabel::new("Raster data is supplied through the node graph").widget_holder());
|
||||||
@@ -743,7 +740,7 @@ pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<Widget
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Group);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
|
||||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
widgets.push(TextLabel::new("Group data is supplied through the node graph").widget_holder());
|
widgets.push(TextLabel::new("Group data is supplied through the node graph").widget_holder());
|
||||||
@@ -754,8 +751,9 @@ pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetH
|
|||||||
pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetHolder> {
|
pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetHolder> {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return Vec::new() };
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return vec![];
|
return vec![];
|
||||||
@@ -825,7 +823,8 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props:
|
|||||||
pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
|
pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return LayoutGroup::Row { widgets: vec![] };
|
return LayoutGroup::Row { widgets: vec![] };
|
||||||
@@ -859,8 +858,9 @@ pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Layout
|
|||||||
pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: ColorInput) -> LayoutGroup {
|
pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: ColorInput) -> LayoutGroup {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||||
// Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel
|
// Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel
|
||||||
let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] else {
|
let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] else {
|
||||||
return LayoutGroup::Row { widgets };
|
return LayoutGroup::Row { widgets };
|
||||||
@@ -913,8 +913,9 @@ pub fn font_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup
|
|||||||
pub fn curve_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
|
pub fn curve_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return LayoutGroup::Row { widgets: vec![] };
|
return LayoutGroup::Row { widgets: vec![] };
|
||||||
@@ -939,14 +940,11 @@ pub fn get_document_node<'a>(node_id: NodeId, context: &'a NodePropertiesContext
|
|||||||
network.nodes.get(&node_id).ok_or(format!("node {node_id} not found in get_document_node"))
|
network.nodes.get(&node_id).ok_or(format!("node {node_id} not found in get_document_node"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_node_and_input_info<'a>(node_id: NodeId, input_index: usize, context: &'a NodePropertiesContext<'a>) -> Result<(&'a DocumentNode, &'a str, &'a str), String> {
|
pub fn query_node_and_input_info<'a>(node_id: NodeId, input_index: usize, context: &'a mut NodePropertiesContext<'a>) -> Result<(&'a DocumentNode, String, String), String> {
|
||||||
|
let (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, input_index, context.selection_network_path);
|
||||||
let document_node = get_document_node(node_id, context)?;
|
let document_node = get_document_node(node_id, context)?;
|
||||||
let input_name = context.network_interface.input_name(node_id, input_index, context.selection_network_path).unwrap_or_else(|| {
|
|
||||||
log::warn!("input name not found in query_node_and_input_info");
|
Ok((document_node, name, description))
|
||||||
""
|
|
||||||
});
|
|
||||||
let input_description = context.network_interface.input_description(node_id, input_index, context.selection_network_path).unwrap_or_default();
|
|
||||||
Ok((document_node, input_name, input_description))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_noise_pattern_state(node_id: NodeId, context: &NodePropertiesContext) -> Result<(bool, bool, bool, bool, bool, bool), String> {
|
pub fn query_noise_pattern_state(node_id: NodeId, context: &NodePropertiesContext) -> Result<(bool, bool, bool, bool, bool, bool), String> {
|
||||||
@@ -995,6 +993,9 @@ pub fn query_assign_colors_randomize(node_id: NodeId, context: &NodePropertiesCo
|
|||||||
pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
use graphene_std::raster::brightness_contrast::*;
|
use graphene_std::raster::brightness_contrast::*;
|
||||||
|
|
||||||
|
// Use Classic
|
||||||
|
let use_classic = bool_widget(ParameterWidgetsInfo::new(node_id, UseClassicInput::INDEX, true, context), CheckboxInput::default());
|
||||||
|
|
||||||
let document_node = match get_document_node(node_id, context) {
|
let document_node = match get_document_node(node_id, context) {
|
||||||
Ok(document_node) => document_node,
|
Ok(document_node) => document_node,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -1002,12 +1003,6 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
|||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use Classic
|
|
||||||
let use_classic = bool_widget(
|
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, UseClassicInput::INDEX, true, context),
|
|
||||||
CheckboxInput::default(),
|
|
||||||
);
|
|
||||||
let use_classic_value = match document_node.inputs[UseClassicInput::INDEX].as_value() {
|
let use_classic_value = match document_node.inputs[UseClassicInput::INDEX].as_value() {
|
||||||
Some(TaggedValue::Bool(use_classic_choice)) => *use_classic_choice,
|
Some(TaggedValue::Bool(use_classic_choice)) => *use_classic_choice,
|
||||||
_ => false,
|
_ => false,
|
||||||
@@ -1015,7 +1010,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
|||||||
|
|
||||||
// Brightness
|
// Brightness
|
||||||
let brightness = number_widget(
|
let brightness = number_widget(
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, BrightnessInput::INDEX, true, context),
|
ParameterWidgetsInfo::new(node_id, BrightnessInput::INDEX, true, context),
|
||||||
NumberInput::default()
|
NumberInput::default()
|
||||||
.unit("%")
|
.unit("%")
|
||||||
.mode_range()
|
.mode_range()
|
||||||
@@ -1026,7 +1021,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
|||||||
|
|
||||||
// Contrast
|
// Contrast
|
||||||
let contrast = number_widget(
|
let contrast = number_widget(
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, ContrastInput::INDEX, true, context),
|
ParameterWidgetsInfo::new(node_id, ContrastInput::INDEX, true, context),
|
||||||
NumberInput::default()
|
NumberInput::default()
|
||||||
.unit("%")
|
.unit("%")
|
||||||
.mode_range()
|
.mode_range()
|
||||||
@@ -1047,6 +1042,11 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
|||||||
pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
use graphene_std::raster::channel_mixer::*;
|
use graphene_std::raster::channel_mixer::*;
|
||||||
|
|
||||||
|
let is_monochrome = bool_widget(ParameterWidgetsInfo::new(node_id, MonochromeInput::INDEX, true, context), CheckboxInput::default());
|
||||||
|
let mut parameter_info = ParameterWidgetsInfo::new(node_id, OutputChannelInput::INDEX, true, context);
|
||||||
|
parameter_info.exposeable = false;
|
||||||
|
let output_channel = enum_choice::<RedGreenBlue>().for_socket(parameter_info).property_row();
|
||||||
|
|
||||||
let document_node = match get_document_node(node_id, context) {
|
let document_node = match get_document_node(node_id, context) {
|
||||||
Ok(document_node) => document_node,
|
Ok(document_node) => document_node,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -1054,22 +1054,12 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
|
|||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Monochrome
|
// Monochrome
|
||||||
let is_monochrome = bool_widget(
|
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, MonochromeInput::INDEX, true, context),
|
|
||||||
CheckboxInput::default(),
|
|
||||||
);
|
|
||||||
let is_monochrome_value = match document_node.inputs[MonochromeInput::INDEX].as_value() {
|
let is_monochrome_value = match document_node.inputs[MonochromeInput::INDEX].as_value() {
|
||||||
Some(TaggedValue::Bool(monochrome_choice)) => *monochrome_choice,
|
Some(TaggedValue::Bool(monochrome_choice)) => *monochrome_choice,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Output channel choice
|
// Output channel choice
|
||||||
let output_channel = enum_choice::<RedGreenBlue>()
|
|
||||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, OutputChannelInput::INDEX, true, context))
|
|
||||||
.exposable(false)
|
|
||||||
.property_row();
|
|
||||||
let output_channel_value = match &document_node.inputs[OutputChannelInput::INDEX].as_value() {
|
let output_channel_value = match &document_node.inputs[OutputChannelInput::INDEX].as_value() {
|
||||||
Some(TaggedValue::RedGreenBlue(choice)) => choice,
|
Some(TaggedValue::RedGreenBlue(choice)) => choice,
|
||||||
_ => {
|
_ => {
|
||||||
@@ -1086,10 +1076,10 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
|
|||||||
(false, RedGreenBlue::Blue) => (BlueRInput::INDEX, BlueGInput::INDEX, BlueBInput::INDEX, BlueCInput::INDEX),
|
(false, RedGreenBlue::Blue) => (BlueRInput::INDEX, BlueGInput::INDEX, BlueBInput::INDEX, BlueCInput::INDEX),
|
||||||
};
|
};
|
||||||
let number_input = NumberInput::default().mode_range().min(-200.).max(200.).unit("%");
|
let number_input = NumberInput::default().mode_range().min(-200.).max(200.).unit("%");
|
||||||
let red = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, red_output_index, true, context), number_input.clone());
|
let red = number_widget(ParameterWidgetsInfo::new(node_id, red_output_index, true, context), number_input.clone());
|
||||||
let green = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, green_output_index, true, context), number_input.clone());
|
let green = number_widget(ParameterWidgetsInfo::new(node_id, green_output_index, true, context), number_input.clone());
|
||||||
let blue = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, blue_output_index, true, context), number_input.clone());
|
let blue = number_widget(ParameterWidgetsInfo::new(node_id, blue_output_index, true, context), number_input.clone());
|
||||||
let constant = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, constant_output_index, true, context), number_input);
|
let constant = number_widget(ParameterWidgetsInfo::new(node_id, constant_output_index, true, context), number_input);
|
||||||
|
|
||||||
// Monochrome
|
// Monochrome
|
||||||
let mut layout = vec![LayoutGroup::Row { widgets: is_monochrome }];
|
let mut layout = vec![LayoutGroup::Row { widgets: is_monochrome }];
|
||||||
@@ -1110,6 +1100,10 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
|
|||||||
pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
use graphene_std::raster::selective_color::*;
|
use graphene_std::raster::selective_color::*;
|
||||||
|
|
||||||
|
let mut default_info = ParameterWidgetsInfo::new(node_id, ColorsInput::INDEX, true, context);
|
||||||
|
default_info.exposeable = false;
|
||||||
|
let colors = enum_choice::<SelectiveColorChoice>().for_socket(default_info).property_row();
|
||||||
|
|
||||||
let document_node = match get_document_node(node_id, context) {
|
let document_node = match get_document_node(node_id, context) {
|
||||||
Ok(document_node) => document_node,
|
Ok(document_node) => document_node,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -1117,13 +1111,7 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
|||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Colors choice
|
// Colors choice
|
||||||
let colors = enum_choice::<SelectiveColorChoice>()
|
|
||||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, ColorsInput::INDEX, true, context))
|
|
||||||
.exposable(false)
|
|
||||||
.property_row();
|
|
||||||
|
|
||||||
let colors_choice = match &document_node.inputs[ColorsInput::INDEX].as_value() {
|
let colors_choice = match &document_node.inputs[ColorsInput::INDEX].as_value() {
|
||||||
Some(TaggedValue::SelectiveColorChoice(choice)) => choice,
|
Some(TaggedValue::SelectiveColorChoice(choice)) => choice,
|
||||||
_ => {
|
_ => {
|
||||||
@@ -1131,7 +1119,6 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
|||||||
return vec![];
|
return vec![];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// CMYK
|
// CMYK
|
||||||
let (c_index, m_index, y_index, k_index) = match colors_choice {
|
let (c_index, m_index, y_index, k_index) = match colors_choice {
|
||||||
SelectiveColorChoice::Reds => (RCInput::INDEX, RMInput::INDEX, RYInput::INDEX, RKInput::INDEX),
|
SelectiveColorChoice::Reds => (RCInput::INDEX, RMInput::INDEX, RYInput::INDEX, RKInput::INDEX),
|
||||||
@@ -1145,14 +1132,14 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
|||||||
SelectiveColorChoice::Blacks => (KCInput::INDEX, KMInput::INDEX, KYInput::INDEX, KKInput::INDEX),
|
SelectiveColorChoice::Blacks => (KCInput::INDEX, KMInput::INDEX, KYInput::INDEX, KKInput::INDEX),
|
||||||
};
|
};
|
||||||
let number_input = NumberInput::default().mode_range().min(-100.).max(100.).unit("%");
|
let number_input = NumberInput::default().mode_range().min(-100.).max(100.).unit("%");
|
||||||
let cyan = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, c_index, true, context), number_input.clone());
|
let cyan = number_widget(ParameterWidgetsInfo::new(node_id, c_index, true, context), number_input.clone());
|
||||||
let magenta = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, m_index, true, context), number_input.clone());
|
let magenta = number_widget(ParameterWidgetsInfo::new(node_id, m_index, true, context), number_input.clone());
|
||||||
let yellow = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, y_index, true, context), number_input.clone());
|
let yellow = number_widget(ParameterWidgetsInfo::new(node_id, y_index, true, context), number_input.clone());
|
||||||
let black = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, k_index, true, context), number_input);
|
let black = number_widget(ParameterWidgetsInfo::new(node_id, k_index, true, context), number_input);
|
||||||
|
|
||||||
// Mode
|
// Mode
|
||||||
let mode = enum_choice::<RelativeAbsolute>()
|
let mode = enum_choice::<RelativeAbsolute>()
|
||||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, ModeInput::INDEX, true, context))
|
.for_socket(ParameterWidgetsInfo::new(node_id, ModeInput::INDEX, true, context))
|
||||||
.property_row();
|
.property_row();
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
@@ -1171,19 +1158,19 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
|||||||
pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
use graphene_std::vector::generator_nodes::grid::*;
|
use graphene_std::vector::generator_nodes::grid::*;
|
||||||
|
|
||||||
let document_node = match get_document_node(node_id, context) {
|
|
||||||
Ok(document_node) => document_node,
|
|
||||||
Err(err) => {
|
|
||||||
log::error!("Could not get document node in exposure_properties: {err}");
|
|
||||||
return Vec::new();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let grid_type = enum_choice::<GridType>()
|
let grid_type = enum_choice::<GridType>()
|
||||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, GridTypeInput::INDEX, true, context))
|
.for_socket(ParameterWidgetsInfo::new(node_id, GridTypeInput::INDEX, true, context))
|
||||||
.property_row();
|
.property_row();
|
||||||
|
|
||||||
let mut widgets = vec![grid_type];
|
let mut widgets = vec![grid_type];
|
||||||
|
|
||||||
|
let document_node = match get_document_node(node_id, context) {
|
||||||
|
Ok(document_node) => document_node,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Could not get document node in grid_properties: {err}");
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
};
|
||||||
let Some(grid_type_input) = document_node.inputs.get(GridTypeInput::INDEX) else {
|
let Some(grid_type_input) = document_node.inputs.get(GridTypeInput::INDEX) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return vec![];
|
return vec![];
|
||||||
@@ -1191,36 +1178,24 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
if let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() {
|
if let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() {
|
||||||
match grid_type {
|
match grid_type {
|
||||||
GridType::Rectangular => {
|
GridType::Rectangular => {
|
||||||
let spacing = coordinate_widget(
|
let spacing = coordinate_widget(ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context), "W", "H", " px", Some(0.));
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::<f64>::INDEX, true, context),
|
|
||||||
"W",
|
|
||||||
"H",
|
|
||||||
" px",
|
|
||||||
Some(0.),
|
|
||||||
);
|
|
||||||
widgets.push(spacing);
|
widgets.push(spacing);
|
||||||
}
|
}
|
||||||
GridType::Isometric => {
|
GridType::Isometric => {
|
||||||
let spacing = LayoutGroup::Row {
|
let spacing = LayoutGroup::Row {
|
||||||
widgets: number_widget(
|
widgets: number_widget(
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::<f64>::INDEX, true, context),
|
ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context),
|
||||||
NumberInput::default().label("H").min(0.).unit(" px"),
|
NumberInput::default().label("H").min(0.).unit(" px"),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
let angles = coordinate_widget(ParameterWidgetsInfo::from_index(document_node, node_id, AnglesInput::INDEX, true, context), "", "", "°", None);
|
let angles = coordinate_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None);
|
||||||
widgets.extend([spacing, angles]);
|
widgets.extend([spacing, angles]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let columns = number_widget(
|
let columns = number_widget(ParameterWidgetsInfo::new(node_id, ColumnsInput::INDEX, true, context), NumberInput::default().min(1.));
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, ColumnsInput::INDEX, true, context),
|
let rows = number_widget(ParameterWidgetsInfo::new(node_id, RowsInput::INDEX, true, context), NumberInput::default().min(1.));
|
||||||
NumberInput::default().min(1.),
|
|
||||||
);
|
|
||||||
let rows = number_widget(
|
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, RowsInput::INDEX, true, context),
|
|
||||||
NumberInput::default().min(1.),
|
|
||||||
);
|
|
||||||
|
|
||||||
widgets.extend([LayoutGroup::Row { widgets: columns }, LayoutGroup::Row { widgets: rows }]);
|
widgets.extend([LayoutGroup::Row { widgets: columns }, LayoutGroup::Row { widgets: rows }]);
|
||||||
|
|
||||||
@@ -1322,26 +1297,14 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp
|
|||||||
let is_quantity = matches!(current_spacing, Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)));
|
let is_quantity = matches!(current_spacing, Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)));
|
||||||
|
|
||||||
let spacing = enum_choice::<PointSpacingType>()
|
let spacing = enum_choice::<PointSpacingType>()
|
||||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::INDEX, true, context))
|
.for_socket(ParameterWidgetsInfo::new(node_id, SpacingInput::INDEX, true, context))
|
||||||
.property_row();
|
.property_row();
|
||||||
let separation = number_widget(
|
let separation = number_widget(ParameterWidgetsInfo::new(node_id, SeparationInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, SeparationInput::INDEX, true, context),
|
let quantity = number_widget(ParameterWidgetsInfo::new(node_id, QuantityInput::INDEX, true, context), NumberInput::default().min(2.).int());
|
||||||
NumberInput::default().min(0.).unit(" px"),
|
let start_offset = number_widget(ParameterWidgetsInfo::new(node_id, StartOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
|
||||||
);
|
let stop_offset = number_widget(ParameterWidgetsInfo::new(node_id, StopOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
|
||||||
let quantity = number_widget(
|
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, QuantityInput::INDEX, true, context),
|
|
||||||
NumberInput::default().min(2.).int(),
|
|
||||||
);
|
|
||||||
let start_offset = number_widget(
|
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, StartOffsetInput::INDEX, true, context),
|
|
||||||
NumberInput::default().min(0.).unit(" px"),
|
|
||||||
);
|
|
||||||
let stop_offset = number_widget(
|
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, StopOffsetInput::INDEX, true, context),
|
|
||||||
NumberInput::default().min(0.).unit(" px"),
|
|
||||||
);
|
|
||||||
let adaptive_spacing = bool_widget(
|
let adaptive_spacing = bool_widget(
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, AdaptiveSpacingInput::INDEX, true, context),
|
ParameterWidgetsInfo::new(node_id, AdaptiveSpacingInput::INDEX, true, context),
|
||||||
CheckboxInput::default().disabled(is_quantity),
|
CheckboxInput::default().disabled(is_quantity),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1361,23 +1324,10 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp
|
|||||||
pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
use graphene_std::raster::exposure::*;
|
use graphene_std::raster::exposure::*;
|
||||||
|
|
||||||
let document_node = match get_document_node(node_id, context) {
|
let exposure = number_widget(ParameterWidgetsInfo::new(node_id, ExposureInput::INDEX, true, context), NumberInput::default().min(-20.).max(20.));
|
||||||
Ok(document_node) => document_node,
|
let offset = number_widget(ParameterWidgetsInfo::new(node_id, OffsetInput::INDEX, true, context), NumberInput::default().min(-0.5).max(0.5));
|
||||||
Err(err) => {
|
|
||||||
log::error!("Could not get document node in exposure_properties: {err}");
|
|
||||||
return Vec::new();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let exposure = number_widget(
|
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, ExposureInput::INDEX, true, context),
|
|
||||||
NumberInput::default().min(-20.).max(20.),
|
|
||||||
);
|
|
||||||
let offset = number_widget(
|
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, OffsetInput::INDEX, true, context),
|
|
||||||
NumberInput::default().min(-0.5).max(0.5),
|
|
||||||
);
|
|
||||||
let gamma_correction = number_widget(
|
let gamma_correction = number_widget(
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, GammaCorrectionInput::INDEX, true, context),
|
ParameterWidgetsInfo::new(node_id, GammaCorrectionInput::INDEX, true, context),
|
||||||
NumberInput::default().min(0.01).max(9.99).increment_step(0.1),
|
NumberInput::default().min(0.01).max(9.99).increment_step(0.1),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1391,6 +1341,14 @@ pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesC
|
|||||||
pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
use graphene_std::vector::generator_nodes::rectangle::*;
|
use graphene_std::vector::generator_nodes::rectangle::*;
|
||||||
|
|
||||||
|
// Corner Radius
|
||||||
|
let mut corner_radius_row_1 = start_widgets(ParameterWidgetsInfo::new(node_id, CornerRadiusInput::<f64>::INDEX, true, context));
|
||||||
|
corner_radius_row_1.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
|
||||||
|
let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()];
|
||||||
|
corner_radius_row_2.push(TextLabel::new("").widget_holder());
|
||||||
|
add_blank_assist(&mut corner_radius_row_2);
|
||||||
|
|
||||||
let document_node = match get_document_node(node_id, context) {
|
let document_node = match get_document_node(node_id, context) {
|
||||||
Ok(document_node) => document_node,
|
Ok(document_node) => document_node,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -1398,23 +1356,6 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Size X
|
|
||||||
let size_x = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, WidthInput::INDEX, true, context), NumberInput::default());
|
|
||||||
|
|
||||||
// Size Y
|
|
||||||
let size_y = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, HeightInput::INDEX, true, context), NumberInput::default());
|
|
||||||
|
|
||||||
// Corner Radius
|
|
||||||
let mut corner_radius_row_1 = start_widgets(
|
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, CornerRadiusInput::<f64>::INDEX, true, context),
|
|
||||||
FrontendGraphDataType::Number,
|
|
||||||
);
|
|
||||||
corner_radius_row_1.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
|
||||||
|
|
||||||
let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()];
|
|
||||||
corner_radius_row_2.push(TextLabel::new("").widget_holder());
|
|
||||||
add_blank_assist(&mut corner_radius_row_2);
|
|
||||||
|
|
||||||
let Some(input) = document_node.inputs.get(IndividualCornerRadiiInput::INDEX) else {
|
let Some(input) = document_node.inputs.get(IndividualCornerRadiiInput::INDEX) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return vec![];
|
return vec![];
|
||||||
@@ -1508,8 +1449,14 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||||||
corner_radius_row_2.push(input_widget);
|
corner_radius_row_2.push(input_widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Size X
|
||||||
|
let size_x = number_widget(ParameterWidgetsInfo::new(node_id, WidthInput::INDEX, true, context), NumberInput::default());
|
||||||
|
|
||||||
|
// Size Y
|
||||||
|
let size_y = number_widget(ParameterWidgetsInfo::new(node_id, HeightInput::INDEX, true, context), NumberInput::default());
|
||||||
|
|
||||||
// Clamped
|
// Clamped
|
||||||
let clamped = bool_widget(ParameterWidgetsInfo::from_index(document_node, node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());
|
let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
LayoutGroup::Row { widgets: size_x },
|
LayoutGroup::Row { widgets: size_x },
|
||||||
@@ -1561,7 +1508,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
|
|||||||
if let Some(field) = graphene_std::registry::NODE_METADATA
|
if let Some(field) = graphene_std::registry::NODE_METADATA
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get(&proto_node_identifier.name.clone().into_owned())
|
.get(proto_node_identifier)
|
||||||
.and_then(|metadata| metadata.fields.get(input_index))
|
.and_then(|metadata| metadata.fields.get(input_index))
|
||||||
{
|
{
|
||||||
number_options = (field.number_min, field.number_max, field.number_mode_range);
|
number_options = (field.number_min, field.number_max, field.number_mode_range);
|
||||||
@@ -1638,6 +1585,8 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
|
|||||||
pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
use graphene_std::vector::fill::*;
|
use graphene_std::vector::fill::*;
|
||||||
|
|
||||||
|
let mut widgets_first_row = start_widgets(ParameterWidgetsInfo::new(node_id, FillInput::<Color>::INDEX, true, context));
|
||||||
|
|
||||||
let document_node = match get_document_node(node_id, context) {
|
let document_node = match get_document_node(node_id, context) {
|
||||||
Ok(document_node) => document_node,
|
Ok(document_node) => document_node,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -1646,11 +1595,6 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut widgets_first_row = start_widgets(
|
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, FillInput::<Color>::INDEX, true, context),
|
|
||||||
FrontendGraphDataType::General,
|
|
||||||
);
|
|
||||||
|
|
||||||
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), &Some(&TaggedValue::OptionalColor(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (
|
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), &Some(&TaggedValue::OptionalColor(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (
|
||||||
&document_node.inputs[FillInput::<Color>::INDEX].as_value(),
|
&document_node.inputs[FillInput::<Color>::INDEX].as_value(),
|
||||||
&document_node.inputs[BackupColorInput::INDEX].as_value(),
|
&document_node.inputs[BackupColorInput::INDEX].as_value(),
|
||||||
@@ -1829,47 +1773,42 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
|||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let join_value = match &document_node.inputs[JoinInput::INDEX].as_value() {
|
||||||
|
Some(TaggedValue::StrokeJoin(x)) => x,
|
||||||
|
_ => &StrokeJoin::Miter,
|
||||||
|
};
|
||||||
|
|
||||||
let color = color_widget(
|
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, ColorInput::<Option<Color>>::INDEX, true, context),
|
|
||||||
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
|
|
||||||
);
|
|
||||||
let weight = number_widget(
|
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, WeightInput::INDEX, true, context),
|
|
||||||
NumberInput::default().unit(" px").min(0.),
|
|
||||||
);
|
|
||||||
let align = enum_choice::<StrokeAlign>()
|
|
||||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, AlignInput::INDEX, true, context))
|
|
||||||
.property_row();
|
|
||||||
let cap = enum_choice::<StrokeCap>()
|
|
||||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, CapInput::INDEX, true, context))
|
|
||||||
.property_row();
|
|
||||||
let join = enum_choice::<StrokeJoin>()
|
|
||||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, JoinInput::INDEX, true, context))
|
|
||||||
.property_row();
|
|
||||||
let miter_limit = number_widget(
|
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, MiterLimitInput::INDEX, true, context),
|
|
||||||
NumberInput::default().min(0.).disabled({
|
|
||||||
let join_value = match &document_node.inputs[JoinInput::INDEX].as_value() {
|
|
||||||
Some(TaggedValue::StrokeJoin(x)) => x,
|
|
||||||
_ => &StrokeJoin::Miter,
|
|
||||||
};
|
|
||||||
join_value != &StrokeJoin::Miter
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let paint_order = enum_choice::<PaintOrder>()
|
|
||||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, PaintOrderInput::INDEX, true, context))
|
|
||||||
.property_row();
|
|
||||||
let dash_lengths_val = match &document_node.inputs[DashLengthsInput::INDEX].as_value() {
|
let dash_lengths_val = match &document_node.inputs[DashLengthsInput::INDEX].as_value() {
|
||||||
Some(TaggedValue::VecF64(x)) => x,
|
Some(TaggedValue::VecF64(x)) => x,
|
||||||
_ => &vec![],
|
_ => &vec![],
|
||||||
};
|
};
|
||||||
let dash_lengths = array_of_number_widget(
|
let has_dash_lengths = dash_lengths_val.is_empty();
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, DashLengthsInput::INDEX, true, context),
|
let miter_limit_disabled = join_value != &StrokeJoin::Miter;
|
||||||
TextInput::default().centered(true),
|
|
||||||
|
let color = color_widget(
|
||||||
|
ParameterWidgetsInfo::new(node_id, ColorInput::<Option<Color>>::INDEX, true, context),
|
||||||
|
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
|
||||||
);
|
);
|
||||||
let number_input = NumberInput::default().unit(" px").disabled(dash_lengths_val.is_empty());
|
let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(" px").min(0.));
|
||||||
let dash_offset = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, DashOffsetInput::INDEX, true, context), number_input);
|
let align = enum_choice::<StrokeAlign>()
|
||||||
|
.for_socket(ParameterWidgetsInfo::new(node_id, AlignInput::INDEX, true, context))
|
||||||
|
.property_row();
|
||||||
|
let cap = enum_choice::<StrokeCap>().for_socket(ParameterWidgetsInfo::new(node_id, CapInput::INDEX, true, context)).property_row();
|
||||||
|
let join = enum_choice::<StrokeJoin>()
|
||||||
|
.for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context))
|
||||||
|
.property_row();
|
||||||
|
|
||||||
|
let miter_limit = number_widget(
|
||||||
|
ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context),
|
||||||
|
NumberInput::default().min(0.).disabled(miter_limit_disabled),
|
||||||
|
);
|
||||||
|
let paint_order = enum_choice::<PaintOrder>()
|
||||||
|
.for_socket(ParameterWidgetsInfo::new(node_id, PaintOrderInput::INDEX, true, context))
|
||||||
|
.property_row();
|
||||||
|
let disabled_number_input = NumberInput::default().unit(" px").disabled(has_dash_lengths);
|
||||||
|
let dash_lengths = array_of_number_widget(ParameterWidgetsInfo::new(node_id, DashLengthsInput::INDEX, true, context), TextInput::default().centered(true));
|
||||||
|
let number_input = disabled_number_input;
|
||||||
|
let dash_offset = number_widget(ParameterWidgetsInfo::new(node_id, DashOffsetInput::INDEX, true, context), number_input);
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
color,
|
color,
|
||||||
@@ -1887,6 +1826,13 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
|||||||
pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
use graphene_std::vector::offset_path::*;
|
use graphene_std::vector::offset_path::*;
|
||||||
|
|
||||||
|
let number_input = NumberInput::default().unit(" px");
|
||||||
|
let distance = number_widget(ParameterWidgetsInfo::new(node_id, DistanceInput::INDEX, true, context), number_input);
|
||||||
|
|
||||||
|
let join = enum_choice::<StrokeJoin>()
|
||||||
|
.for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context))
|
||||||
|
.property_row();
|
||||||
|
|
||||||
let document_node = match get_document_node(node_id, context) {
|
let document_node = match get_document_node(node_id, context) {
|
||||||
Ok(document_node) => document_node,
|
Ok(document_node) => document_node,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -1894,13 +1840,6 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let number_input = NumberInput::default().unit(" px");
|
|
||||||
let distance = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, DistanceInput::INDEX, true, context), number_input);
|
|
||||||
|
|
||||||
let join = enum_choice::<StrokeJoin>()
|
|
||||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, JoinInput::INDEX, true, context))
|
|
||||||
.property_row();
|
|
||||||
|
|
||||||
let number_input = NumberInput::default().min(0.).disabled({
|
let number_input = NumberInput::default().min(0.).disabled({
|
||||||
let join_val = match &document_node.inputs[JoinInput::INDEX].as_value() {
|
let join_val = match &document_node.inputs[JoinInput::INDEX].as_value() {
|
||||||
Some(TaggedValue::StrokeJoin(x)) => x,
|
Some(TaggedValue::StrokeJoin(x)) => x,
|
||||||
@@ -1908,7 +1847,7 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
};
|
};
|
||||||
join_val != &StrokeJoin::Miter
|
join_val != &StrokeJoin::Miter
|
||||||
});
|
});
|
||||||
let miter_limit = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, MiterLimitInput::INDEX, true, context), number_input);
|
let miter_limit = number_widget(ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context), number_input);
|
||||||
|
|
||||||
vec![LayoutGroup::Row { widgets: distance }, join, LayoutGroup::Row { widgets: miter_limit }]
|
vec![LayoutGroup::Row { widgets: distance }, join, LayoutGroup::Row { widgets: miter_limit }]
|
||||||
}
|
}
|
||||||
@@ -1916,20 +1855,16 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
use graphene_std::math_nodes::math::*;
|
use graphene_std::math_nodes::math::*;
|
||||||
|
|
||||||
let document_node = match get_document_node(node_id, context) {
|
|
||||||
Ok(document_node) => document_node,
|
|
||||||
Err(err) => {
|
|
||||||
log::error!("Could not get document node in offset_path_properties: {err}");
|
|
||||||
return Vec::new();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let expression = (|| {
|
let expression = (|| {
|
||||||
let mut widgets = start_widgets(
|
let mut widgets = start_widgets(ParameterWidgetsInfo::new(node_id, ExpressionInput::INDEX, true, context));
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, ExpressionInput::INDEX, true, context),
|
|
||||||
FrontendGraphDataType::General,
|
|
||||||
);
|
|
||||||
|
|
||||||
|
let document_node = match get_document_node(node_id, context) {
|
||||||
|
Ok(document_node) => document_node,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Could not get document node in offset_path_properties: {err}");
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
};
|
||||||
let Some(input) = document_node.inputs.get(ExpressionInput::INDEX) else {
|
let Some(input) = document_node.inputs.get(ExpressionInput::INDEX) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return vec![];
|
return vec![];
|
||||||
@@ -1962,10 +1897,7 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
|
|||||||
}
|
}
|
||||||
widgets
|
widgets
|
||||||
})();
|
})();
|
||||||
let operand_b = number_widget(
|
let operand_b = number_widget(ParameterWidgetsInfo::new(node_id, OperandBInput::<f64>::INDEX, true, context), NumberInput::default());
|
||||||
ParameterWidgetsInfo::from_index(document_node, node_id, OperandBInput::<f64>::INDEX, true, context),
|
|
||||||
NumberInput::default(),
|
|
||||||
);
|
|
||||||
let operand_a_hint = vec![TextLabel::new("(Operand A is the primary input)").widget_holder()];
|
let operand_a_hint = vec![TextLabel::new("(Operand A is the primary input)").widget_holder()];
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
@@ -1976,44 +1908,37 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ParameterWidgetsInfo<'a> {
|
pub struct ParameterWidgetsInfo<'a> {
|
||||||
document_node: &'a DocumentNode,
|
document_node: Option<&'a DocumentNode>,
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
index: usize,
|
index: usize,
|
||||||
name: &'a str,
|
name: String,
|
||||||
description: &'a str,
|
description: String,
|
||||||
|
input_type: FrontendGraphDataType,
|
||||||
blank_assist: bool,
|
blank_assist: bool,
|
||||||
|
exposeable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ParameterWidgetsInfo<'a> {
|
impl<'a> ParameterWidgetsInfo<'a> {
|
||||||
pub fn new(document_node: &'a DocumentNode, node_id: NodeId, index: usize, name: &'a str, description: &'a str, blank_assist: bool) -> ParameterWidgetsInfo<'a> {
|
pub fn new(node_id: NodeId, index: usize, blank_assist: bool, context: &'a mut NodePropertiesContext) -> ParameterWidgetsInfo<'a> {
|
||||||
|
let (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, index, context.selection_network_path);
|
||||||
|
let input_type = FrontendGraphDataType::from_type(&context.network_interface.input_type(&InputConnector::node(node_id, index), context.selection_network_path).0);
|
||||||
|
let document_node = context.network_interface.document_node(&node_id, context.selection_network_path);
|
||||||
|
|
||||||
ParameterWidgetsInfo {
|
ParameterWidgetsInfo {
|
||||||
document_node,
|
document_node,
|
||||||
node_id,
|
node_id,
|
||||||
index,
|
index,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
|
input_type,
|
||||||
blank_assist,
|
blank_assist,
|
||||||
}
|
exposeable: true,
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_index(document_node: &'a DocumentNode, node_id: NodeId, index: usize, blank_assist: bool, context: &'a NodePropertiesContext) -> ParameterWidgetsInfo<'a> {
|
|
||||||
let name = context.network_interface.input_name(node_id, index, context.selection_network_path).unwrap_or_default();
|
|
||||||
let description = context.network_interface.input_description(node_id, index, context.selection_network_path).unwrap_or_default();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
document_node,
|
|
||||||
node_id,
|
|
||||||
index,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
blank_assist,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod choice {
|
pub mod choice {
|
||||||
use super::ParameterWidgetsInfo;
|
use super::ParameterWidgetsInfo;
|
||||||
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
|
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graphene_std::registry::{ChoiceTypeStatic, ChoiceWidgetHint};
|
use graphene_std::registry::{ChoiceTypeStatic, ChoiceWidgetHint};
|
||||||
@@ -2046,11 +1971,7 @@ pub mod choice {
|
|||||||
|
|
||||||
impl<E: ChoiceTypeStatic + 'static> EnumChoice<E> {
|
impl<E: ChoiceTypeStatic + 'static> EnumChoice<E> {
|
||||||
pub fn for_socket(self, parameter_info: ParameterWidgetsInfo) -> ForSocket<Self> {
|
pub fn for_socket(self, parameter_info: ParameterWidgetsInfo) -> ForSocket<Self> {
|
||||||
ForSocket {
|
ForSocket { widget_factory: self, parameter_info }
|
||||||
widget_factory: self,
|
|
||||||
parameter_info,
|
|
||||||
exposable: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Not yet implemented!
|
/// Not yet implemented!
|
||||||
@@ -2141,7 +2062,6 @@ pub mod choice {
|
|||||||
pub struct ForSocket<'p, W> {
|
pub struct ForSocket<'p, W> {
|
||||||
widget_factory: W,
|
widget_factory: W,
|
||||||
parameter_info: ParameterWidgetsInfo<'p>,
|
parameter_info: ParameterWidgetsInfo<'p>,
|
||||||
exposable: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'p, W> ForSocket<'p, W>
|
impl<'p, W> ForSocket<'p, W>
|
||||||
@@ -2158,14 +2078,14 @@ pub mod choice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exposable(self, exposable: bool) -> Self {
|
|
||||||
Self { exposable, ..self }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn property_row(self) -> LayoutGroup {
|
pub fn property_row(self) -> LayoutGroup {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = self.parameter_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = self.parameter_info;
|
||||||
|
let Some(document_node) = document_node else {
|
||||||
|
log::error!("Could not get document node when building property row for node {:?}", node_id);
|
||||||
|
return LayoutGroup::Row { widgets: Vec::new() };
|
||||||
|
};
|
||||||
|
|
||||||
let mut widgets = super::start_widgets_exposable(self.parameter_info, FrontendGraphDataType::General, self.exposable);
|
let mut widgets = super::start_widgets(self.parameter_info);
|
||||||
|
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Inp
|
|||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graphene_std::Type;
|
use graphene_std::Type;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
pub enum FrontendGraphDataType {
|
pub enum FrontendGraphDataType {
|
||||||
@@ -15,7 +16,7 @@ pub enum FrontendGraphDataType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FrontendGraphDataType {
|
impl FrontendGraphDataType {
|
||||||
fn with_type(input: &Type) -> Self {
|
pub fn from_type(input: &Type) -> Self {
|
||||||
match TaggedValue::from_type_or_none(input) {
|
match TaggedValue::from_type_or_none(input) {
|
||||||
TaggedValue::Image(_) | TaggedValue::RasterData(_) => Self::Raster,
|
TaggedValue::Image(_) | TaggedValue::RasterData(_) => Self::Raster,
|
||||||
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData,
|
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData,
|
||||||
@@ -38,7 +39,7 @@ impl FrontendGraphDataType {
|
|||||||
pub fn displayed_type(input: &Type, type_source: &TypeSource) -> Self {
|
pub fn displayed_type(input: &Type, type_source: &TypeSource) -> Self {
|
||||||
match type_source {
|
match type_source {
|
||||||
TypeSource::Error(_) | TypeSource::RandomProtonodeImplementation => Self::General,
|
TypeSource::Error(_) | TypeSource::RandomProtonodeImplementation => Self::General,
|
||||||
_ => Self::with_type(input),
|
_ => Self::from_type(input),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,7 +51,7 @@ pub struct FrontendGraphInput {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
#[serde(rename = "resolvedType")]
|
#[serde(rename = "resolvedType")]
|
||||||
pub resolved_type: Option<String>,
|
pub resolved_type: String,
|
||||||
#[serde(rename = "validTypes")]
|
#[serde(rename = "validTypes")]
|
||||||
pub valid_types: Vec<String>,
|
pub valid_types: Vec<String>,
|
||||||
#[serde(rename = "connectedTo")]
|
#[serde(rename = "connectedTo")]
|
||||||
@@ -64,7 +65,7 @@ pub struct FrontendGraphOutput {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
#[serde(rename = "resolvedType")]
|
#[serde(rename = "resolvedType")]
|
||||||
pub resolved_type: Option<String>,
|
pub resolved_type: String,
|
||||||
#[serde(rename = "connectedTo")]
|
#[serde(rename = "connectedTo")]
|
||||||
pub connected_to: Vec<InputConnector>,
|
pub connected_to: Vec<InputConnector>,
|
||||||
}
|
}
|
||||||
@@ -96,44 +97,27 @@ pub struct FrontendNode {
|
|||||||
pub ui_only: bool,
|
pub ui_only: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
|
||||||
pub struct FrontendNodeWire {
|
|
||||||
#[serde(rename = "wireStart")]
|
|
||||||
pub wire_start: OutputConnector,
|
|
||||||
#[serde(rename = "wireEnd")]
|
|
||||||
pub wire_end: InputConnector,
|
|
||||||
pub dashed: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
pub struct FrontendNodeType {
|
pub struct FrontendNodeType {
|
||||||
pub name: String,
|
pub name: Cow<'static, str>,
|
||||||
pub category: String,
|
pub category: Cow<'static, str>,
|
||||||
#[serde(rename = "inputTypes")]
|
#[serde(rename = "inputTypes")]
|
||||||
pub input_types: Option<Vec<String>>,
|
pub input_types: Option<Vec<Cow<'static, str>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FrontendNodeType {
|
impl FrontendNodeType {
|
||||||
pub fn new(name: &'static str, category: &'static str) -> Self {
|
pub fn new(name: impl Into<Cow<'static, str>>, category: impl Into<Cow<'static, str>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: name.to_string(),
|
name: name.into(),
|
||||||
category: category.to_string(),
|
category: category.into(),
|
||||||
input_types: None,
|
input_types: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_input_types(name: &'static str, category: &'static str, input_types: Vec<String>) -> Self {
|
pub fn with_input_types(name: impl Into<Cow<'static, str>>, category: impl Into<Cow<'static, str>>, input_types: Vec<Cow<'static, str>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: name.to_string(),
|
name: name.into(),
|
||||||
category: category.to_string(),
|
category: category.into(),
|
||||||
input_types: Some(input_types),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_owned_strings_and_input_types(name: String, category: String, input_types: Vec<String>) -> Self {
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
category,
|
|
||||||
input_types: Some(input_types),
|
input_types: Some(input_types),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,16 +137,6 @@ pub struct Transform {
|
|||||||
pub y: f64,
|
pub y: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
|
||||||
pub struct WirePath {
|
|
||||||
#[serde(rename = "pathString")]
|
|
||||||
pub path_string: String,
|
|
||||||
#[serde(rename = "dataType")]
|
|
||||||
pub data_type: FrontendGraphDataType,
|
|
||||||
pub thick: bool,
|
|
||||||
pub dashed: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
pub struct BoxSelection {
|
pub struct BoxSelection {
|
||||||
#[serde(rename = "startX")]
|
#[serde(rename = "startX")]
|
||||||
@@ -217,39 +191,10 @@ pub struct FrontendClickTargets {
|
|||||||
pub modify_import_export: Vec<String>,
|
pub modify_import_export: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
|
||||||
pub enum GraphWireStyle {
|
|
||||||
#[default]
|
|
||||||
Direct = 0,
|
|
||||||
GridAligned = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for GraphWireStyle {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
GraphWireStyle::GridAligned => write!(f, "Grid-Aligned"),
|
|
||||||
GraphWireStyle::Direct => write!(f, "Direct"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GraphWireStyle {
|
|
||||||
pub fn tooltip_description(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
GraphWireStyle::GridAligned => "Wires follow the grid, running in straight lines between nodes",
|
|
||||||
GraphWireStyle::Direct => "Wires bend to run at an angle directly between nodes",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_direct(&self) -> bool {
|
|
||||||
*self == GraphWireStyle::Direct
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings};
|
use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
pub struct OverlaysMessageData<'a> {
|
pub struct OverlaysMessageData<'a> {
|
||||||
pub visibility_settings: OverlaysVisibilitySettings,
|
pub visibility_settings: OverlaysVisibilitySettings,
|
||||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||||
pub device_pixel_ratio: f64,
|
pub device_pixel_ratio: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
pub struct OverlaysMessageHandler {
|
pub struct OverlaysMessageHandler {
|
||||||
pub overlay_providers: HashSet<OverlayProvider>,
|
pub overlay_providers: HashSet<OverlayProvider>,
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
@@ -16,6 +17,7 @@ pub struct OverlaysMessageHandler {
|
|||||||
context: Option<web_sys::CanvasRenderingContext2d>,
|
context: Option<web_sys::CanvasRenderingContext2d>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessageHandler {
|
impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessageHandler {
|
||||||
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, data: OverlaysMessageData) {
|
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, data: OverlaysMessageData) {
|
||||||
let OverlaysMessageData { visibility_settings, ipp, .. } = data;
|
let OverlaysMessageData { visibility_settings, ipp, .. } = data;
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
|
|||||||
|
|
||||||
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
|
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
|
||||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
|
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
|
||||||
let transform = document.metadata().transform_to_viewport(layer);
|
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||||
if display_path {
|
if display_path {
|
||||||
overlay_context.outline_vector(&vector_data, transform);
|
overlay_context.outline_vector(&vector_data, transform);
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,7 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||||
let transform = document.metadata().transform_to_viewport(layer);
|
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||||
let selected = shape_editor.selected_shape_state.get(&layer);
|
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||||
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
|
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use super::utility_functions::overlay_canvas_context;
|
use super::utility_functions::overlay_canvas_context;
|
||||||
use crate::consts::{
|
use crate::consts::{
|
||||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER,
|
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER,
|
||||||
COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
||||||
};
|
};
|
||||||
use crate::messages::prelude::Message;
|
use crate::messages::prelude::Message;
|
||||||
use bezier_rs::{Bezier, Subpath};
|
use bezier_rs::{Bezier, Subpath};
|
||||||
use core::borrow::Borrow;
|
use core::borrow::Borrow;
|
||||||
use core::f64::consts::{FRAC_PI_2, TAU};
|
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::math::quad::Quad;
|
use graphene_std::math::quad::Quad;
|
||||||
@@ -33,12 +33,14 @@ pub enum OverlaysType {
|
|||||||
HoverOutline,
|
HoverOutline,
|
||||||
SelectionOutline,
|
SelectionOutline,
|
||||||
Pivot,
|
Pivot,
|
||||||
|
Origin,
|
||||||
Path,
|
Path,
|
||||||
Anchors,
|
Anchors,
|
||||||
Handles,
|
Handles,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct OverlaysVisibilitySettings {
|
pub struct OverlaysVisibilitySettings {
|
||||||
pub all: bool,
|
pub all: bool,
|
||||||
pub artboard_name: bool,
|
pub artboard_name: bool,
|
||||||
@@ -49,6 +51,7 @@ pub struct OverlaysVisibilitySettings {
|
|||||||
pub hover_outline: bool,
|
pub hover_outline: bool,
|
||||||
pub selection_outline: bool,
|
pub selection_outline: bool,
|
||||||
pub pivot: bool,
|
pub pivot: bool,
|
||||||
|
pub origin: bool,
|
||||||
pub path: bool,
|
pub path: bool,
|
||||||
pub anchors: bool,
|
pub anchors: bool,
|
||||||
pub handles: bool,
|
pub handles: bool,
|
||||||
@@ -66,6 +69,7 @@ impl Default for OverlaysVisibilitySettings {
|
|||||||
hover_outline: true,
|
hover_outline: true,
|
||||||
selection_outline: true,
|
selection_outline: true,
|
||||||
pivot: true,
|
pivot: true,
|
||||||
|
origin: true,
|
||||||
path: true,
|
path: true,
|
||||||
anchors: true,
|
anchors: true,
|
||||||
handles: true,
|
handles: true,
|
||||||
@@ -110,6 +114,10 @@ impl OverlaysVisibilitySettings {
|
|||||||
self.all && self.pivot
|
self.all && self.pivot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn origin(&self) -> bool {
|
||||||
|
self.all && self.origin
|
||||||
|
}
|
||||||
|
|
||||||
pub fn path(&self) -> bool {
|
pub fn path(&self) -> bool {
|
||||||
self.all && self.path
|
self.all && self.path
|
||||||
}
|
}
|
||||||
@@ -423,10 +431,7 @@ impl OverlayContext {
|
|||||||
|
|
||||||
pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
|
pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
|
||||||
let sign = scale.signum();
|
let sign = scale.signum();
|
||||||
let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap())
|
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
|
||||||
.unwrap()
|
|
||||||
.with_alpha(0.05)
|
|
||||||
.to_rgba_hex_srgb();
|
|
||||||
fill_color.insert(0, '#');
|
fill_color.insert(0, '#');
|
||||||
let fill_color = Some(fill_color.as_str());
|
let fill_color = Some(fill_color.as_str());
|
||||||
self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None);
|
self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None);
|
||||||
@@ -463,10 +468,7 @@ impl OverlayContext {
|
|||||||
|
|
||||||
// Hover ring
|
// Hover ring
|
||||||
if show_hover_ring {
|
if show_hover_ring {
|
||||||
let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).to_rgba_hex_srgb();
|
||||||
.unwrap()
|
|
||||||
.with_alpha(0.5)
|
|
||||||
.to_rgba_hex_srgb();
|
|
||||||
fill_color.insert(0, '#');
|
fill_color.insert(0, '#');
|
||||||
|
|
||||||
self.render_context.set_line_width(HOVER_RING_STROKE_WIDTH);
|
self.render_context.set_line_width(HOVER_RING_STROKE_WIDTH);
|
||||||
@@ -550,6 +552,36 @@ impl OverlayContext {
|
|||||||
self.end_dpi_aware_transform();
|
self.end_dpi_aware_transform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {
|
||||||
|
let (x, y) = (position.round() - DVec2::splat(0.5)).into();
|
||||||
|
let color = color.unwrap_or(COLOR_OVERLAY_YELLOW_DULL);
|
||||||
|
|
||||||
|
self.start_dpi_aware_transform();
|
||||||
|
|
||||||
|
// Draw the background circle with a white fill and blue outline
|
||||||
|
self.render_context.begin_path();
|
||||||
|
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, 0., TAU).expect("Failed to draw the circle");
|
||||||
|
self.render_context.set_fill_style_str(COLOR_OVERLAY_WHITE);
|
||||||
|
self.render_context.fill();
|
||||||
|
self.render_context.set_stroke_style_str(color);
|
||||||
|
self.render_context.stroke();
|
||||||
|
|
||||||
|
// Draw the two blue filled sectors
|
||||||
|
self.render_context.begin_path();
|
||||||
|
// Top-left sector
|
||||||
|
self.render_context.move_to(x, y);
|
||||||
|
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, FRAC_PI_2 + angle, PI + angle).expect("Failed to draw arc");
|
||||||
|
self.render_context.close_path();
|
||||||
|
// Bottom-right sector
|
||||||
|
self.render_context.move_to(x, y);
|
||||||
|
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, PI + FRAC_PI_2 + angle, TAU + angle).expect("Failed to draw arc");
|
||||||
|
self.render_context.close_path();
|
||||||
|
self.render_context.set_fill_style_str(color);
|
||||||
|
self.render_context.fill();
|
||||||
|
|
||||||
|
self.end_dpi_aware_transform();
|
||||||
|
}
|
||||||
|
|
||||||
/// Used by the Pen and Path tools to outline the path of the shape.
|
/// Used by the Pen and Path tools to outline the path of the shape.
|
||||||
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
|
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
|
||||||
self.start_dpi_aware_transform();
|
self.start_dpi_aware_transform();
|
||||||
@@ -599,9 +631,11 @@ impl OverlayContext {
|
|||||||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||||
self.start_dpi_aware_transform();
|
self.start_dpi_aware_transform();
|
||||||
|
|
||||||
|
let color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
|
||||||
|
|
||||||
self.render_context.begin_path();
|
self.render_context.begin_path();
|
||||||
self.bezier_command(bezier, transform, true);
|
self.bezier_command(bezier, transform, true);
|
||||||
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);
|
self.render_context.set_stroke_style_str(&color);
|
||||||
self.render_context.set_line_width(4.);
|
self.render_context.set_line_width(4.);
|
||||||
self.render_context.stroke();
|
self.render_context.stroke();
|
||||||
|
|
||||||
@@ -731,11 +765,11 @@ impl OverlayContext {
|
|||||||
// └──┴──┴──┴──┘
|
// └──┴──┴──┴──┘
|
||||||
let pixels = [(0, 0), (2, 2)];
|
let pixels = [(0, 0), (2, 2)];
|
||||||
for &(x, y) in &pixels {
|
for &(x, y) in &pixels {
|
||||||
let index = (x + y * PATTERN_WIDTH as usize) * 4;
|
let index = (x + y * PATTERN_WIDTH) * 4;
|
||||||
data[index..index + 4].copy_from_slice(&color.to_rgba8_srgb());
|
data[index..index + 4].copy_from_slice(&color.to_rgba8_srgb());
|
||||||
}
|
}
|
||||||
|
|
||||||
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&mut data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();
|
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();
|
||||||
pattern_context.put_image_data(&image_data, 0., 0.).unwrap();
|
pattern_context.put_image_data(&image_data, 0., 0.).unwrap();
|
||||||
let pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, "repeat").unwrap().unwrap();
|
let pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, "repeat").unwrap().unwrap();
|
||||||
|
|
||||||
@@ -780,6 +814,36 @@ impl OverlayContext {
|
|||||||
self.render_context.fill_text(text, 0., 0.).expect("Failed to draw the text at the calculated position");
|
self.render_context.fill_text(text, 0., 0.).expect("Failed to draw the text at the calculated position");
|
||||||
self.render_context.reset_transform().expect("Failed to reset the render context transform");
|
self.render_context.reset_transform().expect("Failed to reset the render context transform");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option<String>) {
|
||||||
|
if translation.x.abs() > 1e-3 {
|
||||||
|
self.dashed_line(quad.top_left(), quad.top_right(), None, None, Some(2.), Some(2.), Some(0.5));
|
||||||
|
|
||||||
|
let width = match typed_string {
|
||||||
|
Some(ref typed_string) => typed_string,
|
||||||
|
None => &format!("{:.2}", translation.x).trim_end_matches('0').trim_end_matches('.').to_string(),
|
||||||
|
};
|
||||||
|
let x_transform = DAffine2::from_translation((quad.top_left() + quad.top_right()) / 2.);
|
||||||
|
self.text(width, COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if translation.y.abs() > 1e-3 {
|
||||||
|
self.dashed_line(quad.top_left(), quad.bottom_left(), None, None, Some(2.), Some(2.), Some(0.5));
|
||||||
|
|
||||||
|
let height = match typed_string {
|
||||||
|
Some(ref typed_string) => typed_string,
|
||||||
|
None => &format!("{:.2}", translation.y).trim_end_matches('0').trim_end_matches('.').to_string(),
|
||||||
|
};
|
||||||
|
let y_transform = DAffine2::from_translation((quad.top_left() + quad.bottom_left()) / 2.);
|
||||||
|
let height_pivot = if translation.x > -1e-3 { Pivot::Start } else { Pivot::End };
|
||||||
|
self.text(height, COLOR_OVERLAY_BLUE, None, y_transform, 3., [height_pivot, Pivot::Middle]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if translation.x.abs() > 1e-3 && translation.y.abs() > 1e-3 {
|
||||||
|
self.line(quad.top_right(), quad.bottom_right(), None, None);
|
||||||
|
self.line(quad.bottom_left(), quad.bottom_right(), None, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Pivot {
|
pub enum Pivot {
|
||||||
|
|||||||
+2
-1
@@ -4,9 +4,10 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
|
|||||||
use crate::messages::portfolio::utility_types::PersistentData;
|
use crate::messages::portfolio::utility_types::PersistentData;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
pub struct PropertiesPanelMessageHandler {}
|
pub struct PropertiesPanelMessageHandler {}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMessageHandlerData<'_>)> for PropertiesPanelMessageHandler {
|
impl MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMessageHandlerData<'_>)> for PropertiesPanelMessageHandler {
|
||||||
fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque<Message>, (persistent_data, data): (&PersistentData, PropertiesPanelMessageHandlerData)) {
|
fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque<Message>, (persistent_data, data): (&PersistentData, PropertiesPanelMessageHandlerData)) {
|
||||||
let PropertiesPanelMessageHandlerData {
|
let PropertiesPanelMessageHandlerData {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use super::network_interface::NodeNetworkInterface;
|
use super::network_interface::NodeNetworkInterface;
|
||||||
use crate::messages::portfolio::document::graph_operation::transform_utils;
|
use crate::messages::portfolio::document::graph_operation::transform_utils;
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
|
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
|
||||||
|
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
|
||||||
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::math::quad::Quad;
|
use graphene_std::math::quad::Quad;
|
||||||
@@ -16,10 +18,11 @@ use std::num::NonZeroU64;
|
|||||||
|
|
||||||
// TODO: To avoid storing a stateful snapshot of some other system's state (which is easily to accidentally get out of sync),
|
// TODO: To avoid storing a stateful snapshot of some other system's state (which is easily to accidentally get out of sync),
|
||||||
// TODO: it might be better to have a system that can query the state of the node network on demand.
|
// TODO: it might be better to have a system that can query the state of the node network on demand.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct DocumentMetadata {
|
pub struct DocumentMetadata {
|
||||||
pub upstream_footprints: HashMap<NodeId, Footprint>,
|
pub upstream_footprints: HashMap<NodeId, Footprint>,
|
||||||
pub local_transforms: HashMap<NodeId, DAffine2>,
|
pub local_transforms: HashMap<NodeId, DAffine2>,
|
||||||
|
pub first_instance_source_ids: HashMap<NodeId, Option<NodeId>>,
|
||||||
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
|
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
|
||||||
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||||
pub clip_targets: HashSet<NodeId>,
|
pub clip_targets: HashSet<NodeId>,
|
||||||
@@ -28,20 +31,6 @@ pub struct DocumentMetadata {
|
|||||||
pub document_to_viewport: DAffine2,
|
pub document_to_viewport: DAffine2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DocumentMetadata {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
upstream_footprints: HashMap::new(),
|
|
||||||
local_transforms: HashMap::new(),
|
|
||||||
structure: HashMap::new(),
|
|
||||||
vector_modify: HashMap::new(),
|
|
||||||
click_targets: HashMap::new(),
|
|
||||||
clip_targets: HashSet::new(),
|
|
||||||
document_to_viewport: DAffine2::IDENTITY,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =================================
|
// =================================
|
||||||
// DocumentMetadata: Layer iterators
|
// DocumentMetadata: Layer iterators
|
||||||
// =================================
|
// =================================
|
||||||
@@ -91,6 +80,36 @@ impl DocumentMetadata {
|
|||||||
footprint * local_transform
|
footprint * local_transform
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn transform_to_viewport_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
|
||||||
|
// We're not allowed to convert the root parent to a node id
|
||||||
|
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||||
|
return self.document_to_viewport;
|
||||||
|
}
|
||||||
|
|
||||||
|
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
|
||||||
|
|
||||||
|
let mut use_local = true;
|
||||||
|
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
|
||||||
|
if let Some(path_node) = graph_layer.upstream_node_id_from_name("Path") {
|
||||||
|
if let Some(&source) = self.first_instance_source_ids.get(&layer.to_node()) {
|
||||||
|
if !network_interface
|
||||||
|
.upstream_flow_back_from_nodes(vec![path_node], &[], FlowType::HorizontalFlow)
|
||||||
|
.any(|upstream| Some(upstream) == source)
|
||||||
|
{
|
||||||
|
use_local = false;
|
||||||
|
info!("Local transform is invalid — using the identity for the local transform instead")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let local_transform = use_local.then(|| self.local_transforms.get(&layer.to_node()).copied()).flatten().unwrap_or_default();
|
||||||
|
|
||||||
|
footprint * local_transform
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_to_document_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
|
||||||
|
self.document_to_viewport.inverse() * self.transform_to_viewport_if_feeds(layer, network_interface)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn transform_to_viewport_with_first_transform_node_if_group(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
|
pub fn transform_to_viewport_with_first_transform_node_if_group(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
|
||||||
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
|
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
|
||||||
let local_transform = self.local_transforms.get(&layer.to_node()).copied();
|
let local_transform = self.local_transforms.get(&layer.to_node()).copied();
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ pub mod misc;
|
|||||||
pub mod network_interface;
|
pub mod network_interface;
|
||||||
pub mod nodes;
|
pub mod nodes;
|
||||||
pub mod transformation;
|
pub mod transformation;
|
||||||
|
pub mod wires;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
|||||||
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
use super::network_interface::NodeNetworkInterface;
|
use super::network_interface::NodeNetworkInterface;
|
||||||
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
|
use glam::DVec2;
|
||||||
use graph_craft::document::{NodeId, NodeNetwork};
|
use graph_craft::document::{NodeId, NodeNetwork};
|
||||||
use serde::ser::SerializeStruct;
|
use serde::ser::SerializeStruct;
|
||||||
|
|
||||||
@@ -98,6 +100,22 @@ impl SelectedNodes {
|
|||||||
.filter(move |&layer| self.layer_visible(layer, network_interface) && !self.layer_locked(layer, network_interface))
|
.filter(move |&layer| self.layer_visible(layer, network_interface) && !self.layer_locked(layer, network_interface))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn selected_visible_and_unlocked_layers_mean_average_origin<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 {
|
||||||
|
let (sum, count) = self
|
||||||
|
.selected_visible_and_unlocked_layers(network_interface)
|
||||||
|
.map(|layer| graph_modification_utils::get_viewport_origin(layer, network_interface))
|
||||||
|
.fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1));
|
||||||
|
if count == 0 { DVec2::ZERO } else { sum / count as f64 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selected_visible_and_unlocked_median_points<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 {
|
||||||
|
let (sum, count) = self
|
||||||
|
.selected_visible_and_unlocked_layers(network_interface)
|
||||||
|
.map(|layer| graph_modification_utils::get_viewport_center(layer, network_interface))
|
||||||
|
.fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1));
|
||||||
|
if count == 0 { DVec2::ZERO } else { sum / count as f64 }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
|
pub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
|
||||||
metadata.all_layers().filter(|layer| self.0.contains(&layer.to_node()))
|
metadata.all_layers().filter(|layer| self.0.contains(&layer.to_node()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use crate::messages::portfolio::document::graph_operation::transform_utils;
|
|||||||
use crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, TransformIn};
|
use crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, TransformIn};
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
|
||||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||||
use crate::messages::tool::utility_types::ToolType;
|
use crate::messages::tool::utility_types::ToolType;
|
||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
@@ -537,17 +536,6 @@ impl<'a> Selected<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mean_average_of_pivots(&mut self) -> DVec2 {
|
|
||||||
let xy_summation = self
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.network_interface))
|
|
||||||
.reduce(|a, b| a + b)
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
xy_summation / self.selected.len() as f64
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn center_of_aabb(&mut self) -> DVec2 {
|
pub fn center_of_aabb(&mut self) -> DVec2 {
|
||||||
let [min, max] = self
|
let [min, max] = self
|
||||||
.selected
|
.selected
|
||||||
|
|||||||
@@ -0,0 +1,589 @@
|
|||||||
|
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
|
||||||
|
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||||
|
use glam::{DVec2, IVec2};
|
||||||
|
use graphene_std::uuid::NodeId;
|
||||||
|
use graphene_std::vector::PointId;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
pub struct WirePath {
|
||||||
|
#[serde(rename = "pathString")]
|
||||||
|
pub path_string: String,
|
||||||
|
#[serde(rename = "dataType")]
|
||||||
|
pub data_type: FrontendGraphDataType,
|
||||||
|
pub thick: bool,
|
||||||
|
pub dashed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
pub struct WirePathUpdate {
|
||||||
|
pub id: NodeId,
|
||||||
|
#[serde(rename = "inputIndex")]
|
||||||
|
pub input_index: usize,
|
||||||
|
// If none, then remove the wire from the map
|
||||||
|
#[serde(rename = "wirePathUpdate")]
|
||||||
|
pub wire_path_update: Option<WirePath>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
pub enum GraphWireStyle {
|
||||||
|
#[default]
|
||||||
|
Direct = 0,
|
||||||
|
GridAligned = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for GraphWireStyle {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
GraphWireStyle::GridAligned => write!(f, "Grid-Aligned"),
|
||||||
|
GraphWireStyle::Direct => write!(f, "Direct"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphWireStyle {
|
||||||
|
pub fn tooltip_description(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
GraphWireStyle::GridAligned => "Wires follow the grid, running in straight lines between nodes",
|
||||||
|
GraphWireStyle::Direct => "Wires bend to run at an angle directly between nodes",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_direct(&self) -> bool {
|
||||||
|
*self == GraphWireStyle::Direct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> Subpath<PointId> {
|
||||||
|
let grid_spacing = 24.;
|
||||||
|
match graph_wire_style {
|
||||||
|
GraphWireStyle::Direct => {
|
||||||
|
let horizontal_gap = (output_position.x - input_position.x).abs();
|
||||||
|
let vertical_gap = (output_position.y - input_position.y).abs();
|
||||||
|
|
||||||
|
let curve_length = grid_spacing;
|
||||||
|
let curve_falloff_rate = curve_length * std::f64::consts::TAU;
|
||||||
|
|
||||||
|
let horizontal_curve_amount = -(2_f64.powf((-10. * horizontal_gap) / curve_falloff_rate)) + 1.;
|
||||||
|
let vertical_curve_amount = -(2_f64.powf((-10. * vertical_gap) / curve_falloff_rate)) + 1.;
|
||||||
|
let horizontal_curve = horizontal_curve_amount * curve_length;
|
||||||
|
let vertical_curve = vertical_curve_amount * curve_length;
|
||||||
|
|
||||||
|
let locations = [
|
||||||
|
output_position,
|
||||||
|
DVec2::new(
|
||||||
|
if vertical_out { output_position.x } else { output_position.x + horizontal_curve },
|
||||||
|
if vertical_out { output_position.y - vertical_curve } else { output_position.y },
|
||||||
|
),
|
||||||
|
DVec2::new(
|
||||||
|
if vertical_in { input_position.x } else { input_position.x - horizontal_curve },
|
||||||
|
if vertical_in { input_position.y + vertical_curve } else { input_position.y },
|
||||||
|
),
|
||||||
|
DVec2::new(input_position.x, input_position.y),
|
||||||
|
];
|
||||||
|
|
||||||
|
let smoothing = 0.5;
|
||||||
|
let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing);
|
||||||
|
let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing);
|
||||||
|
|
||||||
|
Subpath::new(
|
||||||
|
vec![
|
||||||
|
ManipulatorGroup {
|
||||||
|
anchor: locations[0],
|
||||||
|
in_handle: None,
|
||||||
|
out_handle: None,
|
||||||
|
id: PointId::generate(),
|
||||||
|
},
|
||||||
|
ManipulatorGroup {
|
||||||
|
anchor: locations[1],
|
||||||
|
in_handle: None,
|
||||||
|
out_handle: Some(locations[1] + delta01),
|
||||||
|
id: PointId::generate(),
|
||||||
|
},
|
||||||
|
ManipulatorGroup {
|
||||||
|
anchor: locations[2],
|
||||||
|
in_handle: Some(locations[2] - delta23),
|
||||||
|
out_handle: None,
|
||||||
|
id: PointId::generate(),
|
||||||
|
},
|
||||||
|
ManipulatorGroup {
|
||||||
|
anchor: locations[3],
|
||||||
|
in_handle: None,
|
||||||
|
out_handle: None,
|
||||||
|
id: PointId::generate(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
GraphWireStyle::GridAligned => {
|
||||||
|
let locations = straight_wire_paths(output_position, input_position, vertical_out, vertical_in);
|
||||||
|
straight_wire_subpath(locations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn straight_wire_paths(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<IVec2> {
|
||||||
|
let grid_spacing = 24;
|
||||||
|
let line_width = 2;
|
||||||
|
|
||||||
|
let in_x = input_position.x as i32;
|
||||||
|
let in_y = input_position.y as i32;
|
||||||
|
let out_x = output_position.x as i32;
|
||||||
|
let out_y = output_position.y as i32;
|
||||||
|
|
||||||
|
let mid_x = (in_x + out_x) / 2 + (((in_x + out_x) / 2) % grid_spacing);
|
||||||
|
let mid_y = (in_y + out_y) / 2 + (((in_y + out_y) / 2) % grid_spacing);
|
||||||
|
let mid_y_alternate = (in_y + in_y) / 2 - (((in_y + in_y) / 2) % grid_spacing);
|
||||||
|
|
||||||
|
let x1 = out_x;
|
||||||
|
let x2 = out_x + grid_spacing;
|
||||||
|
let x3 = in_x - 2 * grid_spacing;
|
||||||
|
let x4 = in_x;
|
||||||
|
let x5 = in_x - 2 * grid_spacing + line_width;
|
||||||
|
let x6 = out_x + grid_spacing + line_width;
|
||||||
|
let x7 = out_x + 2 * grid_spacing + line_width;
|
||||||
|
let x8 = in_x + line_width;
|
||||||
|
let x9 = out_x + 2 * grid_spacing;
|
||||||
|
let x10 = mid_x + line_width;
|
||||||
|
let x11 = out_x - grid_spacing;
|
||||||
|
let x12 = out_x - 4 * grid_spacing;
|
||||||
|
let x13 = mid_x;
|
||||||
|
let x14 = in_x + grid_spacing;
|
||||||
|
let x15 = in_x - 4 * grid_spacing;
|
||||||
|
let x16 = in_x + 8 * grid_spacing;
|
||||||
|
let x17 = mid_x - 2 * line_width;
|
||||||
|
let x18 = out_x + grid_spacing - 2 * line_width;
|
||||||
|
let x19 = out_x - 2 * line_width;
|
||||||
|
let x20 = mid_x - line_width;
|
||||||
|
|
||||||
|
let y1 = out_y;
|
||||||
|
let y2 = out_y - grid_spacing;
|
||||||
|
let y3 = in_y;
|
||||||
|
let y4 = out_y - grid_spacing + 5 * line_width + 1;
|
||||||
|
let y5 = in_y - 2 * grid_spacing;
|
||||||
|
let y6 = out_y + 4 * line_width;
|
||||||
|
let y7 = out_y + 5 * line_width;
|
||||||
|
let y8 = out_y - 2 * grid_spacing + 5 * line_width + 1;
|
||||||
|
let y9 = out_y + 6 * line_width;
|
||||||
|
let y10 = in_y + 2 * grid_spacing;
|
||||||
|
let y111 = in_y + grid_spacing + 6 * line_width + 1;
|
||||||
|
let y12 = in_y + grid_spacing - 5 * line_width + 1;
|
||||||
|
let y13 = in_y - grid_spacing;
|
||||||
|
let y14 = in_y + grid_spacing;
|
||||||
|
let y15 = mid_y;
|
||||||
|
let y16 = mid_y_alternate;
|
||||||
|
|
||||||
|
let wire1 = vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x5, y4), IVec2::new(x5, y3), IVec2::new(x4, y3)];
|
||||||
|
|
||||||
|
let wire2 = vec![IVec2::new(x1, y1), IVec2::new(x1, y16), IVec2::new(x3, y16), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||||
|
|
||||||
|
let wire3 = vec![
|
||||||
|
IVec2::new(x1, y1),
|
||||||
|
IVec2::new(x1, y4),
|
||||||
|
IVec2::new(x12, y4),
|
||||||
|
IVec2::new(x12, y10),
|
||||||
|
IVec2::new(x3, y10),
|
||||||
|
IVec2::new(x3, y3),
|
||||||
|
IVec2::new(x4, y3),
|
||||||
|
];
|
||||||
|
|
||||||
|
let wire4 = vec![
|
||||||
|
IVec2::new(x1, y1),
|
||||||
|
IVec2::new(x1, y4),
|
||||||
|
IVec2::new(x13, y4),
|
||||||
|
IVec2::new(x13, y10),
|
||||||
|
IVec2::new(x3, y10),
|
||||||
|
IVec2::new(x3, y3),
|
||||||
|
IVec2::new(x4, y3),
|
||||||
|
];
|
||||||
|
|
||||||
|
if out_y == in_y && out_x > in_x && (vertical_out || !vertical_in) {
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x3, y2), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// `outConnector` point and `inConnector` point lying on the same horizontal grid line and `outConnector` point lies to the right of `inConnector` point
|
||||||
|
if out_y == in_y && out_x > in_x && (vertical_out || !vertical_in) {
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x3, y2), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle straight lines
|
||||||
|
if out_y == in_y || (out_x == in_x && vertical_out) {
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x4, y3)];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle standard right-angle paths
|
||||||
|
// Start vertical, then horizontal
|
||||||
|
|
||||||
|
// `outConnector` point lies to the left of `inConnector` point
|
||||||
|
if vertical_out && in_x > out_x {
|
||||||
|
// `outConnector` point lies above `inConnector` point
|
||||||
|
if out_y < in_y {
|
||||||
|
// `outConnector` point lies on the vertical grid line 4 units to the left of `inConnector` point point
|
||||||
|
if -4 * grid_spacing <= out_x - in_x && out_x - in_x < -3 * grid_spacing {
|
||||||
|
return wire1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// `outConnector` point lying on vertical grid lines 3 and 2 units to the left of `inConnector` point
|
||||||
|
if -3 * grid_spacing <= out_x - in_x && out_x - in_x <= -grid_spacing {
|
||||||
|
if -2 * grid_spacing <= out_y - in_y && out_y - in_y <= -grid_spacing {
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x1, y2), IVec2::new(x2, y2), IVec2::new(x2, y3), IVec2::new(x4, y3)];
|
||||||
|
};
|
||||||
|
|
||||||
|
if -grid_spacing <= out_y - in_y && out_y - in_y <= 0 {
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x6, y4), IVec2::new(x6, y3), IVec2::new(x4, y3)];
|
||||||
|
};
|
||||||
|
|
||||||
|
return vec![
|
||||||
|
IVec2::new(x1, y1),
|
||||||
|
IVec2::new(x1, y4),
|
||||||
|
IVec2::new(x7, y4),
|
||||||
|
IVec2::new(x7, y5),
|
||||||
|
IVec2::new(x3, y5),
|
||||||
|
IVec2::new(x3, y3),
|
||||||
|
IVec2::new(x4, y3),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// `outConnector` point lying on vertical grid line 1 units to the left of `inConnector` point
|
||||||
|
if -grid_spacing < out_x - in_x && out_x - in_x <= 0 {
|
||||||
|
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
|
||||||
|
if -2 * grid_spacing <= out_y - in_y && out_y - in_y <= -grid_spacing {
|
||||||
|
return vec![IVec2::new(x1, y6), IVec2::new(x2, y6), IVec2::new(x8, y3)];
|
||||||
|
};
|
||||||
|
|
||||||
|
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
|
||||||
|
if -grid_spacing <= out_y - in_y && out_y - in_y <= 0 {
|
||||||
|
return vec![IVec2::new(x1, y7), IVec2::new(x4, y3)];
|
||||||
|
};
|
||||||
|
|
||||||
|
return vec![
|
||||||
|
IVec2::new(x1, y1),
|
||||||
|
IVec2::new(x1, y2),
|
||||||
|
IVec2::new(x9, y2),
|
||||||
|
IVec2::new(x9, y5),
|
||||||
|
IVec2::new(x3, y5),
|
||||||
|
IVec2::new(x3, y3),
|
||||||
|
IVec2::new(x4, y3),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x10, y4), IVec2::new(x10, y3), IVec2::new(x4, y3)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// `outConnector` point lies below `inConnector` point
|
||||||
|
// `outConnector` point lying on vertical grid line 1 unit to the left of `inConnector` point
|
||||||
|
if -grid_spacing <= out_x - in_x && out_x - in_x <= 0 {
|
||||||
|
// `outConnector` point lying on the horizontal grid lines 1 and 2 units below the `inConnector` point
|
||||||
|
if 0 <= out_y - in_y && out_y - in_y <= 2 * grid_spacing {
|
||||||
|
return vec![IVec2::new(x1, y6), IVec2::new(x11, y6), IVec2::new(x11, y3), IVec2::new(x4, y3)];
|
||||||
|
};
|
||||||
|
|
||||||
|
return wire2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x1, y3), IVec2::new(x4, y3)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// `outConnector` point lies to the right of `inConnector` point
|
||||||
|
if vertical_out && in_x <= out_x {
|
||||||
|
// `outConnector` point lying on any horizontal grid line above `inConnector` point
|
||||||
|
if out_y < in_y {
|
||||||
|
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
|
||||||
|
if -2 * grid_spacing < out_y - in_y && out_y - in_y <= -grid_spacing {
|
||||||
|
return wire1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
|
||||||
|
if -grid_spacing < out_y - in_y && out_y - in_y <= 0 {
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x1, y8), IVec2::new(x5, y8), IVec2::new(x5, y3), IVec2::new(x4, y3)];
|
||||||
|
};
|
||||||
|
|
||||||
|
// `outConnector` point lying on vertical grid lines 1 and 2 units to the right of `inConnector` point
|
||||||
|
if grid_spacing <= out_x - in_x && out_x - in_x <= 3 * grid_spacing {
|
||||||
|
return vec![
|
||||||
|
IVec2::new(x1, y1),
|
||||||
|
IVec2::new(x1, y4),
|
||||||
|
IVec2::new(x9, y4),
|
||||||
|
IVec2::new(x9, y5),
|
||||||
|
IVec2::new(x3, y5),
|
||||||
|
IVec2::new(x3, y3),
|
||||||
|
IVec2::new(x4, y3),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec![
|
||||||
|
IVec2::new(x1, y1),
|
||||||
|
IVec2::new(x1, y4),
|
||||||
|
IVec2::new(x10, y4),
|
||||||
|
IVec2::new(x10, y5),
|
||||||
|
IVec2::new(x5, y5),
|
||||||
|
IVec2::new(x5, y3),
|
||||||
|
IVec2::new(x4, y3),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// `outConnector` point lies below `inConnector` point
|
||||||
|
if out_y - in_y <= grid_spacing {
|
||||||
|
// `outConnector` point lies on the horizontal grid line 1 unit below the `inConnector` Point
|
||||||
|
if 0 <= out_x - in_x && out_x - in_x <= 13 * grid_spacing {
|
||||||
|
return vec![IVec2::new(x1, y9), IVec2::new(x3, y9), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||||
|
};
|
||||||
|
|
||||||
|
if 13 < out_x - in_x && out_x - in_x <= 18 * grid_spacing {
|
||||||
|
return wire3;
|
||||||
|
};
|
||||||
|
|
||||||
|
return wire4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `outConnector` point lies on the horizontal grid line 2 units below `outConnector` point
|
||||||
|
if grid_spacing <= out_y - in_y && out_y - in_y <= 2 * grid_spacing {
|
||||||
|
if 0 <= out_x - in_x && out_x - in_x <= 13 * grid_spacing {
|
||||||
|
return vec![IVec2::new(x1, y7), IVec2::new(x5, y7), IVec2::new(x5, y3), IVec2::new(x4, y3)];
|
||||||
|
};
|
||||||
|
|
||||||
|
if 13 < out_x - in_x && out_x - in_x <= 18 * grid_spacing {
|
||||||
|
return wire3;
|
||||||
|
};
|
||||||
|
|
||||||
|
return wire4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 to 4 units below the `outConnector` Point
|
||||||
|
if out_y - in_y <= 4 * grid_spacing {
|
||||||
|
return wire1;
|
||||||
|
};
|
||||||
|
|
||||||
|
return wire2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start horizontal, then vertical
|
||||||
|
if vertical_in {
|
||||||
|
// when `outConnector` lies below `inConnector`
|
||||||
|
if out_y > in_y {
|
||||||
|
// `out_x` lies to the left of `in_x`
|
||||||
|
if out_x < in_x {
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x4, y1), IVec2::new(x4, y3)];
|
||||||
|
};
|
||||||
|
|
||||||
|
// `out_x` lies to the right of `in_x`
|
||||||
|
if out_y - in_y <= grid_spacing {
|
||||||
|
// `outConnector` point directly below `inConnector` point
|
||||||
|
if 0 <= out_x - in_x && out_x - in_x <= grid_spacing {
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x14, y1), IVec2::new(x14, y2), IVec2::new(x4, y2), IVec2::new(x4, y3)];
|
||||||
|
};
|
||||||
|
|
||||||
|
// `outConnector` point lies below `inConnector` point and strictly to the right of `inConnector` point
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y111), IVec2::new(x4, y111), IVec2::new(x4, y3)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x4, y2), IVec2::new(x4, y3)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// `out_y` lies on or above the `in_y` point
|
||||||
|
if -6 * grid_spacing < in_x - out_x && in_x - out_x < 4 * grid_spacing {
|
||||||
|
// edge case: `outConnector` point lying on vertical grid lines ranging from 4 units to left to 5 units to right of `inConnector` point
|
||||||
|
if -grid_spacing < in_x - out_x && in_x - out_x < 4 * grid_spacing {
|
||||||
|
return vec![
|
||||||
|
IVec2::new(x1, y1),
|
||||||
|
IVec2::new(x2, y1),
|
||||||
|
IVec2::new(x2, y2),
|
||||||
|
IVec2::new(x15, y2),
|
||||||
|
IVec2::new(x15, y12),
|
||||||
|
IVec2::new(x4, y12),
|
||||||
|
IVec2::new(x4, y3),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x16, y1), IVec2::new(x16, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// left of edge case: `outConnector` point lying on vertical grid lines more than 4 units to left of `inConnector` point
|
||||||
|
if 4 * grid_spacing < in_x - out_x {
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x17, y1), IVec2::new(x17, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];
|
||||||
|
};
|
||||||
|
|
||||||
|
// right of edge case: `outConnector` point lying on the vertical grid lines more than 5 units to right of `inConnector` point
|
||||||
|
if 6 * grid_spacing > in_x - out_x {
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x18, y1), IVec2::new(x18, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both horizontal - use horizontal middle point
|
||||||
|
// When `inConnector` point is one of the two closest diagonally opposite points
|
||||||
|
if 0 <= in_x - out_x && in_x - out_x <= grid_spacing && in_y - out_y >= -grid_spacing && in_y - out_y <= grid_spacing {
|
||||||
|
return vec![IVec2::new(x19, y1), IVec2::new(x19, y3), IVec2::new(x4, y3)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// When `inConnector` point lies on the horizontal line 1 unit above and below the `outConnector` point
|
||||||
|
if -grid_spacing <= out_y - in_y && out_y - in_y <= grid_spacing && out_x > in_x {
|
||||||
|
// Horizontal line above `out_y`
|
||||||
|
if in_y < out_y {
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y13), IVec2::new(x3, y13), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Horizontal line below `out_y`
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y14), IVec2::new(x3, y14), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// `outConnector` point to the right of `inConnector` point
|
||||||
|
if out_x > in_x - grid_spacing {
|
||||||
|
return vec![
|
||||||
|
IVec2::new(x1, y1),
|
||||||
|
IVec2::new(x18, y1),
|
||||||
|
IVec2::new(x18, y15),
|
||||||
|
IVec2::new(x5, y15),
|
||||||
|
IVec2::new(x5, y3),
|
||||||
|
IVec2::new(x4, y3),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
// When `inConnector` point lies on the vertical grid line two units to the right of `outConnector` point
|
||||||
|
if grid_spacing <= in_x - out_x && in_x - out_x <= 2 * grid_spacing {
|
||||||
|
return vec![IVec2::new(x1, y1), IVec2::new(x18, y1), IVec2::new(x18, y3), IVec2::new(x4, y3)];
|
||||||
|
};
|
||||||
|
|
||||||
|
vec![IVec2::new(x1, y1), IVec2::new(x20, y1), IVec2::new(x20, y3), IVec2::new(x4, y3)]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn straight_wire_subpath(locations: Vec<IVec2>) -> Subpath<PointId> {
|
||||||
|
if locations.is_empty() {
|
||||||
|
return Subpath::new(Vec::new(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if locations.len() == 2 {
|
||||||
|
return Subpath::new(
|
||||||
|
vec![
|
||||||
|
ManipulatorGroup {
|
||||||
|
anchor: locations[0].into(),
|
||||||
|
in_handle: None,
|
||||||
|
out_handle: None,
|
||||||
|
id: PointId::generate(),
|
||||||
|
},
|
||||||
|
ManipulatorGroup {
|
||||||
|
anchor: locations[1].into(),
|
||||||
|
in_handle: None,
|
||||||
|
out_handle: None,
|
||||||
|
id: PointId::generate(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let corner_radius = 10;
|
||||||
|
|
||||||
|
// Create path with rounded corners
|
||||||
|
let mut path = vec![ManipulatorGroup {
|
||||||
|
anchor: locations[0].into(),
|
||||||
|
in_handle: None,
|
||||||
|
out_handle: None,
|
||||||
|
id: PointId::generate(),
|
||||||
|
}];
|
||||||
|
|
||||||
|
for i in 1..(locations.len() - 1) {
|
||||||
|
let prev = locations[i - 1];
|
||||||
|
let curr = locations[i];
|
||||||
|
let next = locations[i + 1];
|
||||||
|
|
||||||
|
let corner_start = IVec2::new(
|
||||||
|
curr.x
|
||||||
|
+ if curr.x == prev.x {
|
||||||
|
0
|
||||||
|
} else if prev.x > curr.x {
|
||||||
|
corner_radius
|
||||||
|
} else {
|
||||||
|
-corner_radius
|
||||||
|
},
|
||||||
|
curr.y
|
||||||
|
+ if curr.y == prev.y {
|
||||||
|
0
|
||||||
|
} else if prev.y > curr.y {
|
||||||
|
corner_radius
|
||||||
|
} else {
|
||||||
|
-corner_radius
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let corner_start_mid = IVec2::new(
|
||||||
|
curr.x
|
||||||
|
+ if curr.x == prev.x {
|
||||||
|
0
|
||||||
|
} else if prev.x > curr.x {
|
||||||
|
corner_radius / 2
|
||||||
|
} else {
|
||||||
|
-corner_radius / 2
|
||||||
|
},
|
||||||
|
curr.y
|
||||||
|
+ if curr.y == prev.y {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
match prev.y > curr.y {
|
||||||
|
true => corner_radius / 2,
|
||||||
|
false => -corner_radius / 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let corner_end = IVec2::new(
|
||||||
|
curr.x
|
||||||
|
+ if curr.x == next.x {
|
||||||
|
0
|
||||||
|
} else if next.x > curr.x {
|
||||||
|
corner_radius
|
||||||
|
} else {
|
||||||
|
-corner_radius
|
||||||
|
},
|
||||||
|
curr.y
|
||||||
|
+ if curr.y == next.y {
|
||||||
|
0
|
||||||
|
} else if next.y > curr.y {
|
||||||
|
corner_radius
|
||||||
|
} else {
|
||||||
|
-corner_radius
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let corner_end_mid = IVec2::new(
|
||||||
|
curr.x
|
||||||
|
+ if curr.x == next.x {
|
||||||
|
0
|
||||||
|
} else if next.x > curr.x {
|
||||||
|
corner_radius / 2
|
||||||
|
} else {
|
||||||
|
-corner_radius / 2
|
||||||
|
},
|
||||||
|
curr.y
|
||||||
|
+ if curr.y == next.y {
|
||||||
|
0
|
||||||
|
} else if next.y > curr.y {
|
||||||
|
10 / 2
|
||||||
|
} else {
|
||||||
|
-corner_radius / 2
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
path.extend(vec![
|
||||||
|
ManipulatorGroup {
|
||||||
|
anchor: corner_start.into(),
|
||||||
|
in_handle: None,
|
||||||
|
out_handle: Some(corner_start_mid.into()),
|
||||||
|
id: PointId::generate(),
|
||||||
|
},
|
||||||
|
ManipulatorGroup {
|
||||||
|
anchor: corner_end.into(),
|
||||||
|
in_handle: Some(corner_end_mid.into()),
|
||||||
|
out_handle: None,
|
||||||
|
id: PointId::generate(),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
path.push(ManipulatorGroup {
|
||||||
|
anchor: (*locations.last().unwrap()).into(),
|
||||||
|
in_handle: None,
|
||||||
|
out_handle: None,
|
||||||
|
id: PointId::generate(),
|
||||||
|
});
|
||||||
|
Subpath::new(path, false)
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate,
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use graphene_std::path_bool::BooleanOperation;
|
use graphene_std::path_bool::BooleanOperation;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
pub struct MenuBarMessageHandler {
|
pub struct MenuBarMessageHandler {
|
||||||
pub has_active_document: bool,
|
pub has_active_document: bool,
|
||||||
pub canvas_tilted: bool,
|
pub canvas_tilted: bool,
|
||||||
@@ -21,6 +21,7 @@ pub struct MenuBarMessageHandler {
|
|||||||
pub reset_node_definitions_on_open: bool,
|
pub reset_node_definitions_on_open: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<MenuBarMessage, ()> for MenuBarMessageHandler {
|
impl MessageHandler<MenuBarMessage, ()> for MenuBarMessageHandler {
|
||||||
fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||||
match message {
|
match message {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
|||||||
use crate::messages::portfolio::document::DocumentMessageData;
|
use crate::messages::portfolio::document::DocumentMessageData;
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||||
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
||||||
|
use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector;
|
||||||
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
||||||
use crate::messages::portfolio::document_migration::*;
|
use crate::messages::portfolio::document_migration::*;
|
||||||
use crate::messages::preferences::SelectionMode;
|
use crate::messages::preferences::SelectionMode;
|
||||||
@@ -24,6 +25,7 @@ use graphene_std::renderer::Quad;
|
|||||||
use graphene_std::text::Font;
|
use graphene_std::text::Font;
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
pub struct PortfolioMessageData<'a> {
|
pub struct PortfolioMessageData<'a> {
|
||||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||||
pub preferences: &'a PreferencesMessageHandler,
|
pub preferences: &'a PreferencesMessageHandler,
|
||||||
@@ -34,7 +36,7 @@ pub struct PortfolioMessageData<'a> {
|
|||||||
pub animation: &'a AnimationMessageHandler,
|
pub animation: &'a AnimationMessageHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, ExtractField)]
|
||||||
pub struct PortfolioMessageHandler {
|
pub struct PortfolioMessageHandler {
|
||||||
menu_bar_message_handler: MenuBarMessageHandler,
|
menu_bar_message_handler: MenuBarMessageHandler,
|
||||||
pub documents: HashMap<DocumentId, DocumentMessageHandler>,
|
pub documents: HashMap<DocumentId, DocumentMessageHandler>,
|
||||||
@@ -51,6 +53,7 @@ pub struct PortfolioMessageHandler {
|
|||||||
pub reset_node_definitions_on_open: bool,
|
pub reset_node_definitions_on_open: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMessageHandler {
|
impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMessageHandler {
|
||||||
fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque<Message>, data: PortfolioMessageData) {
|
fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque<Message>, data: PortfolioMessageData) {
|
||||||
let PortfolioMessageData {
|
let PortfolioMessageData {
|
||||||
@@ -426,6 +429,43 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||||||
// Upgrade the document's nodes to be compatible with the latest version
|
// Upgrade the document's nodes to be compatible with the latest version
|
||||||
document_migration_upgrades(&mut document, reset_node_definitions_on_open);
|
document_migration_upgrades(&mut document, reset_node_definitions_on_open);
|
||||||
|
|
||||||
|
// Ensure each node has the metadata for its inputs
|
||||||
|
for (node_id, node, path) in document.network_interface.document_network().clone().recursive_nodes() {
|
||||||
|
document.network_interface.validate_input_metadata(node_id, node, &path);
|
||||||
|
document.network_interface.validate_display_name_metadata(node_id, &path);
|
||||||
|
document.network_interface.validate_output_names(node_id, node, &path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure layers are positioned as stacks if they are upstream siblings of another layer
|
||||||
|
document.network_interface.load_structure();
|
||||||
|
let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();
|
||||||
|
for layer in all_layers {
|
||||||
|
let Some((downstream_node, input_index)) = document
|
||||||
|
.network_interface
|
||||||
|
.outward_wires(&[])
|
||||||
|
.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0)))
|
||||||
|
.and_then(|outward_wires| outward_wires.first())
|
||||||
|
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
|
||||||
|
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
|
||||||
|
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
|
||||||
|
let (Some(layer_position), Some(downstream_position)) =
|
||||||
|
(document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[]))
|
||||||
|
else {
|
||||||
|
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if layer_position.x == downstream_position.x {
|
||||||
|
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set the save state of the document based on what's given to us by the caller to this message
|
// Set the save state of the document based on what's given to us by the caller to this message
|
||||||
document.set_auto_save_state(document_is_auto_saved);
|
document.set_auto_save_state(document_is_auto_saved);
|
||||||
document.set_save_state(document_is_saved);
|
document.set_save_state(document_is_saved);
|
||||||
@@ -709,7 +749,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||||||
}
|
}
|
||||||
|
|
||||||
let Some(document) = self.documents.get_mut(&document_id) else {
|
let Some(document) = self.documents.get_mut(&document_id) else {
|
||||||
warn!("Tried to read non existant document");
|
warn!("Tried to read non existent document");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !document.is_loaded {
|
if !document.is_loaded {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use std::any::Any;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// The spreadsheet UI allows for instance data to be previewed.
|
/// The spreadsheet UI allows for instance data to be previewed.
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone, ExtractField)]
|
||||||
pub struct SpreadsheetMessageHandler {
|
pub struct SpreadsheetMessageHandler {
|
||||||
/// Sets whether or not the spreadsheet is drawn.
|
/// Sets whether or not the spreadsheet is drawn.
|
||||||
pub spreadsheet_view_open: bool,
|
pub spreadsheet_view_open: bool,
|
||||||
@@ -25,6 +25,7 @@ pub struct SpreadsheetMessageHandler {
|
|||||||
viewing_vector_data_domain: VectorDataDomain,
|
viewing_vector_data_domain: VectorDataDomain,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler {
|
impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler {
|
||||||
fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||||
match message {
|
match message {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
|
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||||
use crate::messages::preferences::SelectionMode;
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use crate::consts::VIEWPORT_ZOOM_WHEEL_RATE;
|
use crate::consts::VIEWPORT_ZOOM_WHEEL_RATE;
|
||||||
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
||||||
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
|
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||||
use crate::messages::preferences::SelectionMode;
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use graph_craft::wasm_application_io::EditorPreferences;
|
use graph_craft::wasm_application_io::EditorPreferences;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)]
|
||||||
pub struct PreferencesMessageHandler {
|
pub struct PreferencesMessageHandler {
|
||||||
pub selection_mode: SelectionMode,
|
pub selection_mode: SelectionMode,
|
||||||
pub zoom_with_scroll: bool,
|
pub zoom_with_scroll: bool,
|
||||||
@@ -44,6 +44,7 @@ impl Default for PreferencesMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
||||||
fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||||
match message {
|
match message {
|
||||||
@@ -86,7 +87,8 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
|||||||
}
|
}
|
||||||
PreferencesMessage::GraphWireStyle { style } => {
|
PreferencesMessage::GraphWireStyle { style } => {
|
||||||
self.graph_wire_style = style;
|
self.graph_wire_style = style;
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::UnloadWires);
|
||||||
|
responses.add(NodeGraphMessage::SendWires);
|
||||||
}
|
}
|
||||||
PreferencesMessage::ViewportZoomWheelRate { rate } => {
|
PreferencesMessage::ViewportZoomWheelRate { rate } => {
|
||||||
self.viewport_zoom_wheel_rate = rate;
|
self.viewport_zoom_wheel_rate = rate;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Root
|
// Root
|
||||||
pub use crate::utility_traits::{ActionList, AsMessage, MessageHandler, ToDiscriminant, TransitiveChild};
|
pub use crate::utility_traits::{ActionList, AsMessage, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild};
|
||||||
|
pub use crate::utility_types::{DebugMessageTree, MessageData};
|
||||||
// Message, MessageData, MessageDiscriminant, MessageHandler
|
// Message, MessageData, MessageDiscriminant, MessageHandler
|
||||||
pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler};
|
pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler};
|
||||||
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
|
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use crate::consts::{COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER};
|
use crate::consts::{COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER};
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
|
||||||
use crate::messages::prelude::DocumentMessageHandler;
|
use crate::messages::prelude::DocumentMessageHandler;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use std::f64::consts::FRAC_PI_2;
|
use std::f64::consts::FRAC_PI_2;
|
||||||
@@ -10,25 +9,32 @@ pub struct CompassRose {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CompassRose {
|
impl CompassRose {
|
||||||
fn get_layer_pivot_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
|
|
||||||
let [min, max] = document.metadata().nonzero_bounding_box(layer);
|
|
||||||
|
|
||||||
let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
|
||||||
let layer_transform = document.metadata().transform_to_viewport(layer);
|
|
||||||
layer_transform * bounds_transform
|
|
||||||
}
|
|
||||||
pub fn refresh_position(&mut self, document: &DocumentMessageHandler) {
|
pub fn refresh_position(&mut self, document: &DocumentMessageHandler) {
|
||||||
let selected_nodes = document.network_interface.selected_nodes();
|
let selected = document.network_interface.selected_nodes();
|
||||||
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface);
|
|
||||||
|
|
||||||
let Some(first) = layers.next() else { return };
|
if !selected.has_selected_nodes() {
|
||||||
let count = layers.count() + 1;
|
return;
|
||||||
let transform = if count == 1 {
|
}
|
||||||
Self::get_layer_pivot_transform(first, document)
|
|
||||||
} else {
|
let transform = selected
|
||||||
let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||||
DAffine2::from_translation(min) * DAffine2::from_scale(max - min)
|
.find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||||
};
|
.map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let bounds = document
|
||||||
|
.network_interface
|
||||||
|
.selected_nodes()
|
||||||
|
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||||
|
.filter_map(|layer| {
|
||||||
|
document
|
||||||
|
.metadata()
|
||||||
|
.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer))
|
||||||
|
})
|
||||||
|
.reduce(graphene_std::renderer::Quad::combine_bounds);
|
||||||
|
|
||||||
|
let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||||
|
let transform = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||||
|
|
||||||
self.compass_center = transform.transform_point2(DVec2::splat(0.5));
|
self.compass_center = transform.transform_point2(DVec2::splat(0.5));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Flo
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use bezier_rs::Subpath;
|
use bezier_rs::Subpath;
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use graph_craft::concrete;
|
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{NodeId, NodeInput};
|
use graph_craft::document::{NodeId, NodeInput};
|
||||||
|
use graph_craft::{ProtoNodeIdentifier, concrete};
|
||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::NodeInputDecleration;
|
use graphene_std::NodeInputDecleration;
|
||||||
use graphene_std::raster::BlendMode;
|
use graphene_std::raster::BlendMode;
|
||||||
@@ -243,20 +243,26 @@ pub fn new_custom(id: NodeId, nodes: Vec<(NodeId, NodeTemplate)>, parent: LayerN
|
|||||||
LayerNodeIdentifier::new_unchecked(id)
|
LayerNodeIdentifier::new_unchecked(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Locate the final pivot from the transform (TODO: decide how the pivot should actually work)
|
/// Locate the origin of the transform node
|
||||||
pub fn get_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DVec2> {
|
pub fn get_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DVec2> {
|
||||||
let pivot_node_input_index = 5;
|
use graphene_std::transform_nodes::transform::TranslateInput;
|
||||||
if let TaggedValue::DVec2(pivot) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", pivot_node_input_index)? {
|
|
||||||
Some(*pivot)
|
if let TaggedValue::DVec2(origin) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", TranslateInput::INDEX)? {
|
||||||
|
Some(*origin)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_viewport_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {
|
pub fn get_viewport_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {
|
||||||
|
let origin = get_origin(layer, network_interface).unwrap_or_default();
|
||||||
|
network_interface.document_metadata().document_to_viewport.transform_point2(origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_viewport_center(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {
|
||||||
let [min, max] = network_interface.document_metadata().nonzero_bounding_box(layer);
|
let [min, max] = network_interface.document_metadata().nonzero_bounding_box(layer);
|
||||||
let pivot = get_pivot(layer, network_interface).unwrap_or(DVec2::splat(0.5));
|
let center = DVec2::splat(0.5);
|
||||||
network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * pivot)
|
network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * center)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current gradient of a layer from the closest "Fill" node.
|
/// Get the current gradient of a layer from the closest "Fill" node.
|
||||||
@@ -420,14 +426,14 @@ impl<'a> NodeGraphLayer<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Node id of a protonode if it exists in the layer's primary flow
|
/// Node id of a protonode if it exists in the layer's primary flow
|
||||||
pub fn upstream_node_id_from_protonode(&self, protonode_identifier: &'static str) -> Option<NodeId> {
|
pub fn upstream_node_id_from_protonode(&self, protonode_identifier: ProtoNodeIdentifier) -> Option<NodeId> {
|
||||||
self.horizontal_layer_flow()
|
self.horizontal_layer_flow()
|
||||||
// Take until a different layer is reached
|
// Take until a different layer is reached
|
||||||
.take_while(|&node_id| node_id == self.layer_node || !self.network_interface.is_layer(&node_id, &[]))
|
.take_while(|&node_id| node_id == self.layer_node || !self.network_interface.is_layer(&node_id, &[]))
|
||||||
.find(move |node_id| {
|
.find(|node_id| {
|
||||||
self.network_interface
|
self.network_interface
|
||||||
.implementation(node_id, &[])
|
.implementation(node_id, &[])
|
||||||
.is_some_and(move |implementation| *implementation == graph_craft::document::DocumentNodeImplementation::proto(protonode_identifier))
|
.is_some_and(|implementation| *implementation == graph_craft::document::DocumentNodeImplementation::ProtoNode(protonode_identifier.clone()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,184 @@
|
|||||||
//! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale and origin of the layer.
|
//! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale.
|
||||||
|
|
||||||
use super::graph_modification_utils;
|
|
||||||
use crate::consts::PIVOT_DIAMETER;
|
use crate::consts::PIVOT_DIAMETER;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
|
use crate::messages::tool::tool_messages::path_tool::PathOptionsUpdate;
|
||||||
|
use crate::messages::tool::tool_messages::select_tool::SelectOptionsUpdate;
|
||||||
|
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene_std::transform::ReferencePoint;
|
use graphene_std::{transform::ReferencePoint, vector::ManipulatorPointId};
|
||||||
use std::collections::VecDeque;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
pub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) -> WidgetHolder {
|
||||||
|
IconButton::new(if active { "PinActive" } else { "PinInactive" }, 24)
|
||||||
|
.tooltip(String::from(if active { "Unpin Custom Pivot" } else { "Pin Custom Pivot" }) + "\n\nUnless pinned, the pivot will return to its prior reference point when a new selection is made.")
|
||||||
|
.disabled(!enabled)
|
||||||
|
.on_update(move |_| match source {
|
||||||
|
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotPinned).into(),
|
||||||
|
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotPinned).into(),
|
||||||
|
})
|
||||||
|
.widget_holder()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pivot_reference_point_widget(disabled: bool, reference_point: ReferencePoint, source: PivotToolSource) -> WidgetHolder {
|
||||||
|
ReferencePointInput::new(reference_point)
|
||||||
|
.tooltip("Custom Pivot Reference Point\n\nPlaces the pivot at a corner, edge, or center of the selection bounds, unless it is dragged elsewhere.")
|
||||||
|
.disabled(disabled)
|
||||||
|
.on_update(move |pivot_input: &ReferencePointInput| match source {
|
||||||
|
PivotToolSource::Select => SelectToolMessage::SetPivot { position: pivot_input.value }.into(),
|
||||||
|
PivotToolSource::Path => PathToolMessage::SetPivot { position: pivot_input.value }.into(),
|
||||||
|
})
|
||||||
|
.widget_holder()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource) -> Vec<WidgetHolder> {
|
||||||
|
let gizmo_type_entries = [PivotGizmoType::Pivot, PivotGizmoType::Average, PivotGizmoType::Active]
|
||||||
|
.iter()
|
||||||
|
.map(|gizmo_type| {
|
||||||
|
MenuListEntry::new(format!("{gizmo_type:?}")).label(gizmo_type.to_string()).on_commit({
|
||||||
|
let value = source.clone();
|
||||||
|
move |_| match value {
|
||||||
|
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::PivotGizmoType(*gizmo_type)).into(),
|
||||||
|
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::PivotGizmoType(*gizmo_type)).into(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
vec![
|
||||||
|
CheckboxInput::new(!state.disabled)
|
||||||
|
.tooltip(
|
||||||
|
"Pivot Gizmo\n\
|
||||||
|
\n\
|
||||||
|
Enabled: the chosen gizmo type is shown and used to control rotation and scaling.\n\
|
||||||
|
Disabled: rotation and scaling occurs about the center of the selection bounds.",
|
||||||
|
)
|
||||||
|
.on_update(move |optional_input: &CheckboxInput| match source {
|
||||||
|
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(),
|
||||||
|
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(),
|
||||||
|
})
|
||||||
|
.widget_holder(),
|
||||||
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
|
DropdownInput::new(vec![gizmo_type_entries])
|
||||||
|
.selected_index(Some(match state.gizmo_type {
|
||||||
|
PivotGizmoType::Pivot => 0,
|
||||||
|
PivotGizmoType::Average => 1,
|
||||||
|
PivotGizmoType::Active => 2,
|
||||||
|
}))
|
||||||
|
.tooltip(
|
||||||
|
"Pivot Gizmo Type\n\
|
||||||
|
\n\
|
||||||
|
Selects which gizmo type is shown and used as the center of rotation/scaling transformations.\n\
|
||||||
|
\n\
|
||||||
|
Custom Pivot: rotates and scales relative to the selection bounds, or elsewhere if dragged.\n\
|
||||||
|
Origin (Average Point): rotates and scales about the average point of all selected layer origins.\n\
|
||||||
|
Origin (Active Object): rotates and scales about the origin of the most recently selected layer.",
|
||||||
|
)
|
||||||
|
.disabled(state.disabled)
|
||||||
|
.widget_holder(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum PivotToolSource {
|
||||||
|
Path,
|
||||||
|
#[default]
|
||||||
|
Select,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct PivotGizmo {
|
||||||
|
pub pivot: Pivot,
|
||||||
|
pub state: PivotGizmoState,
|
||||||
|
pub layer: Option<LayerNodeIdentifier>,
|
||||||
|
pub point: Option<ManipulatorPointId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PivotGizmo {
|
||||||
|
pub fn position(&self, document: &DocumentMessageHandler) -> DVec2 {
|
||||||
|
let network = &document.network_interface;
|
||||||
|
(!self.state.disabled)
|
||||||
|
.then_some({
|
||||||
|
match self.state.gizmo_type {
|
||||||
|
PivotGizmoType::Average => Some(network.selected_nodes().selected_visible_and_unlocked_layers_mean_average_origin(network)),
|
||||||
|
PivotGizmoType::Pivot => self.pivot.pivot,
|
||||||
|
PivotGizmoType::Active => self.layer.map(|layer| graph_modification_utils::get_viewport_origin(layer, network)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or_else(|| self.pivot.transform_from_normalized.transform_point2(DVec2::splat(0.5)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recalculate_transform(&mut self, document: &DocumentMessageHandler) -> DAffine2 {
|
||||||
|
self.pivot.recalculate_pivot(document);
|
||||||
|
self.pivot.transform_from_normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pin_active(&self) -> bool {
|
||||||
|
self.pivot.pinned && self.state.is_pivot_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pivot_disconnected(&self) -> bool {
|
||||||
|
self.pivot.old_pivot_position == ReferencePoint::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
pub enum PivotGizmoType {
|
||||||
|
// Pivot
|
||||||
|
#[default]
|
||||||
|
Pivot,
|
||||||
|
// Origin
|
||||||
|
Average,
|
||||||
|
Active,
|
||||||
|
// TODO: Add "Individual"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
pub struct PivotGizmoState {
|
||||||
|
pub disabled: bool,
|
||||||
|
pub gizmo_type: PivotGizmoType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PivotGizmoState {
|
||||||
|
pub fn is_pivot_type(&self) -> bool {
|
||||||
|
self.gizmo_type == PivotGizmoType::Pivot || self.disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_pivot(&self) -> bool {
|
||||||
|
self.gizmo_type == PivotGizmoType::Pivot && !self.disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PivotGizmoType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
PivotGizmoType::Pivot => write!(f, "Custom Pivot"),
|
||||||
|
PivotGizmoType::Average => write!(f, "Origin (Average Point)"),
|
||||||
|
PivotGizmoType::Active => write!(f, "Origin (Active Object)"),
|
||||||
|
// TODO: Add "Origin (Individual)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct Pivot {
|
pub struct Pivot {
|
||||||
/// Pivot between (0,0) and (1,1)
|
/// Pivot between (0,0) and (1,1)
|
||||||
normalized_pivot: DVec2,
|
normalized_pivot: DVec2,
|
||||||
/// Transform to get from normalized pivot to viewspace
|
/// Transform to get from normalized pivot to viewspace
|
||||||
transform_from_normalized: DAffine2,
|
pub transform_from_normalized: DAffine2,
|
||||||
/// The viewspace pivot position (if applicable)
|
/// The viewspace pivot position
|
||||||
pivot: Option<DVec2>,
|
pub pivot: Option<DVec2>,
|
||||||
/// The old pivot position in the GUI, used to reduce refreshes of the document bar
|
/// The old pivot position in the GUI, used to reduce refreshes of the document bar
|
||||||
old_pivot_position: ReferencePoint,
|
pub old_pivot_position: ReferencePoint,
|
||||||
|
/// The last ReferencePoint which wasn't none
|
||||||
|
pub last_non_none_reference_point: ReferencePoint,
|
||||||
/// Used to enable and disable the pivot
|
/// Used to enable and disable the pivot
|
||||||
active: bool,
|
pub pinned: bool,
|
||||||
|
/// Had selected_visible_and_unlocked_layers
|
||||||
|
pub empty: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Pivot {
|
impl Default for Pivot {
|
||||||
@@ -30,84 +188,62 @@ impl Default for Pivot {
|
|||||||
transform_from_normalized: Default::default(),
|
transform_from_normalized: Default::default(),
|
||||||
pivot: Default::default(),
|
pivot: Default::default(),
|
||||||
old_pivot_position: ReferencePoint::Center,
|
old_pivot_position: ReferencePoint::Center,
|
||||||
active: true,
|
last_non_none_reference_point: ReferencePoint::Center,
|
||||||
|
pinned: false,
|
||||||
|
empty: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pivot {
|
impl Pivot {
|
||||||
/// Calculates the transform that gets from normalized pivot to viewspace.
|
|
||||||
fn get_layer_pivot_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
|
|
||||||
let [min, max] = document.metadata().nonzero_bounding_box(layer);
|
|
||||||
|
|
||||||
let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
|
||||||
let layer_transform = document.metadata().transform_to_viewport(layer);
|
|
||||||
layer_transform * bounds_transform
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recomputes the pivot position and transform.
|
/// Recomputes the pivot position and transform.
|
||||||
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
|
pub fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
|
||||||
if !self.active {
|
let selected = document.network_interface.selected_nodes();
|
||||||
|
self.empty = !selected.has_selected_nodes();
|
||||||
|
if !selected.has_selected_nodes() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let selected_nodes = document.network_interface.selected_nodes();
|
let transform = selected
|
||||||
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface);
|
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||||
let Some(first) = layers.next() else {
|
.find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||||
// If no layers are selected then we revert things back to default
|
.map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let bounds = document
|
||||||
|
.network_interface
|
||||||
|
.selected_nodes()
|
||||||
|
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||||
|
.filter_map(|layer| {
|
||||||
|
document
|
||||||
|
.metadata()
|
||||||
|
.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer))
|
||||||
|
})
|
||||||
|
.reduce(graphene_std::renderer::Quad::combine_bounds);
|
||||||
|
|
||||||
|
let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||||
|
self.transform_from_normalized = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||||
|
|
||||||
|
if self.old_pivot_position != ReferencePoint::None {
|
||||||
|
self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recalculate_pivot_for_layer(&mut self, document: &DocumentMessageHandler, bounds: Option<[DVec2; 2]>) {
|
||||||
|
let selected = document.network_interface.selected_nodes();
|
||||||
|
if !selected.has_selected_nodes() {
|
||||||
self.normalized_pivot = DVec2::splat(0.5);
|
self.normalized_pivot = DVec2::splat(0.5);
|
||||||
self.pivot = None;
|
self.pivot = None;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add one because the first item is consumed above.
|
let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||||
let selected_layers_count = layers.count() + 1;
|
self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||||
|
self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot));
|
||||||
// If just one layer is selected we can use its inner transform (as it accounts for rotation)
|
|
||||||
if selected_layers_count == 1 {
|
|
||||||
let normalized_pivot = graph_modification_utils::get_pivot(first, &document.network_interface).unwrap_or(DVec2::splat(0.5));
|
|
||||||
self.normalized_pivot = normalized_pivot;
|
|
||||||
self.transform_from_normalized = Self::get_layer_pivot_transform(first, document);
|
|
||||||
self.pivot = Some(self.transform_from_normalized.transform_point2(normalized_pivot));
|
|
||||||
} else {
|
|
||||||
// If more than one layer is selected we use the AABB with the mean of the pivots
|
|
||||||
let xy_summation = document
|
|
||||||
.network_interface
|
|
||||||
.selected_nodes()
|
|
||||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
|
||||||
.map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.network_interface))
|
|
||||||
.reduce(|a, b| a + b)
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let pivot = xy_summation / selected_layers_count as f64;
|
|
||||||
self.pivot = Some(pivot);
|
|
||||||
let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
|
||||||
self.normalized_pivot = (pivot - min) / (max - min);
|
|
||||||
|
|
||||||
self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, draw_data: Option<(f64,)>) {
|
|
||||||
if !overlay_context.visibility_settings.pivot() {
|
|
||||||
self.active = false;
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
self.active = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.recalculate_pivot(document);
|
|
||||||
if let (Some(pivot), Some(data)) = (self.pivot, draw_data) {
|
|
||||||
overlay_context.pivot(pivot, data.0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas).
|
/// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas).
|
||||||
pub fn should_refresh_pivot_position(&mut self) -> bool {
|
pub fn should_refresh_pivot_position(&mut self) -> bool {
|
||||||
if !self.active {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let new = self.to_pivot_position();
|
let new = self.to_pivot_position();
|
||||||
let should_refresh = new != self.old_pivot_position;
|
let should_refresh = new != self.old_pivot_position;
|
||||||
self.old_pivot_position = new;
|
self.old_pivot_position = new;
|
||||||
@@ -118,37 +254,24 @@ impl Pivot {
|
|||||||
self.normalized_pivot.into()
|
self.normalized_pivot.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the viewport position of the pivot for all selected layers.
|
/// Sets the viewport position of the pivot.
|
||||||
pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
pub fn set_viewport_position(&mut self, position: DVec2) {
|
||||||
if !self.active {
|
if self.transform_from_normalized.matrix2.determinant().abs() <= f64::EPSILON {
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
|
|
||||||
for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {
|
self.normalized_pivot = self.transform_from_normalized.inverse().transform_point2(position);
|
||||||
let transform = Self::get_layer_pivot_transform(layer, document);
|
self.pivot = Some(position);
|
||||||
// Only update the pivot when computed position is finite.
|
|
||||||
if transform.matrix2.determinant().abs() <= f64::EPSILON {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let pivot = transform.inverse().transform_point2(position);
|
|
||||||
responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the pivot using the normalized transform that is set above.
|
/// Set the pivot using a normalized position.
|
||||||
pub fn set_normalized_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
pub fn set_normalized_position(&mut self, position: DVec2) {
|
||||||
if !self.active {
|
self.normalized_pivot = position;
|
||||||
return;
|
self.pivot = Some(self.transform_from_normalized.transform_point2(position));
|
||||||
}
|
|
||||||
|
|
||||||
self.set_viewport_position(self.transform_from_normalized.transform_point2(position), document, responses);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Answers if the pointer is currently positioned over the pivot.
|
/// Answers if the pointer is currently positioned over the pivot.
|
||||||
pub fn is_over(&self, mouse: DVec2) -> bool {
|
pub fn is_over(&self, mouse: DVec2) -> bool {
|
||||||
if !self.active {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some()
|
self.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,14 @@ impl SelectedLayerState {
|
|||||||
self.selected_segments.remove(&segment);
|
self.selected_segments.remove(&segment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deselect_all_points_in_layer(&mut self) {
|
||||||
|
self.selected_points.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deselect_all_segments_in_layer(&mut self) {
|
||||||
|
self.selected_segments.clear();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear_points(&mut self) {
|
pub fn clear_points(&mut self) {
|
||||||
self.selected_points.clear();
|
self.selected_points.clear();
|
||||||
}
|
}
|
||||||
@@ -204,15 +212,15 @@ impl ClosestSegment {
|
|||||||
self.bezier_point_to_viewport
|
self.bezier_point_to_viewport
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn closest_point(&self, document_metadata: &DocumentMetadata) -> DVec2 {
|
pub fn closest_point(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> DVec2 {
|
||||||
let transform = document_metadata.transform_to_viewport(self.layer);
|
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||||
let bezier_point = self.bezier.evaluate(TValue::Parametric(self.t));
|
let bezier_point = self.bezier.evaluate(TValue::Parametric(self.t));
|
||||||
transform.transform_point2(bezier_point)
|
transform.transform_point2(bezier_point)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates this [`ClosestSegment`] with the viewport-space location of the closest point on the segment to the given mouse position.
|
/// Updates this [`ClosestSegment`] with the viewport-space location of the closest point on the segment to the given mouse position.
|
||||||
pub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, mouse_position: DVec2) {
|
pub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface, mouse_position: DVec2) {
|
||||||
let transform = document_metadata.transform_to_viewport(self.layer);
|
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||||
let layer_mouse_pos = transform.inverse().transform_point2(mouse_position);
|
let layer_mouse_pos = transform.inverse().transform_point2(mouse_position);
|
||||||
|
|
||||||
let t = self.bezier.project(layer_mouse_pos).clamp(0., 1.);
|
let t = self.bezier.project(layer_mouse_pos).clamp(0., 1.);
|
||||||
@@ -231,9 +239,9 @@ impl ClosestSegment {
|
|||||||
tolerance.powi(2) < self.distance_squared(mouse_position)
|
tolerance.powi(2) < self.distance_squared(mouse_position)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_positions(&self, document_metadata: &DocumentMetadata) -> (Option<DVec2>, Option<DVec2>) {
|
pub fn handle_positions(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> (Option<DVec2>, Option<DVec2>) {
|
||||||
// Transform to viewport space
|
// Transform to viewport space
|
||||||
let transform = document_metadata.transform_to_viewport(self.layer);
|
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||||
|
|
||||||
// Split the Bezier at the parameter `t`
|
// Split the Bezier at the parameter `t`
|
||||||
let [first, second] = self.bezier.split(TValue::Parametric(self.t));
|
let [first, second] = self.bezier.split(TValue::Parametric(self.t));
|
||||||
@@ -299,7 +307,7 @@ impl ClosestSegment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_perp(&self, document: &DocumentMessageHandler) -> DVec2 {
|
pub fn calculate_perp(&self, document: &DocumentMessageHandler) -> DVec2 {
|
||||||
let tangent = if let (Some(handle1), Some(handle2)) = self.handle_positions(document.metadata()) {
|
let tangent = if let (Some(handle1), Some(handle2)) = self.handle_positions(document.metadata(), &document.network_interface) {
|
||||||
(handle1 - handle2).try_normalize()
|
(handle1 - handle2).try_normalize()
|
||||||
} else {
|
} else {
|
||||||
let [first_point, last_point] = self.points();
|
let [first_point, last_point] = self.points();
|
||||||
@@ -331,7 +339,7 @@ impl ClosestSegment {
|
|||||||
break_colinear_molding: bool,
|
break_colinear_molding: bool,
|
||||||
temporary_adjacent_handles_while_molding: Option<[Option<HandleId>; 2]>,
|
temporary_adjacent_handles_while_molding: Option<[Option<HandleId>; 2]>,
|
||||||
) -> Option<[Option<HandleId>; 2]> {
|
) -> Option<[Option<HandleId>; 2]> {
|
||||||
let transform = document.metadata().transform_to_viewport(self.layer);
|
let transform = document.metadata().transform_to_viewport_if_feeds(self.layer, &document.network_interface);
|
||||||
|
|
||||||
let start = self.bezier.start;
|
let start = self.bezier.start;
|
||||||
let end = self.bezier.end;
|
let end = self.bezier.end;
|
||||||
@@ -388,6 +396,10 @@ impl ClosestSegment {
|
|||||||
|
|
||||||
// TODO Consider keeping a list of selected manipulators to minimize traversals of the layers
|
// TODO Consider keeping a list of selected manipulators to minimize traversals of the layers
|
||||||
impl ShapeState {
|
impl ShapeState {
|
||||||
|
pub fn is_selected_layer(&self, layer: LayerNodeIdentifier) -> bool {
|
||||||
|
self.selected_shape_state.contains_key(&layer)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_point_ignored(&self, point: &ManipulatorPointId) -> bool {
|
pub fn is_point_ignored(&self, point: &ManipulatorPointId) -> bool {
|
||||||
(point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors)
|
(point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors)
|
||||||
}
|
}
|
||||||
@@ -495,7 +507,7 @@ impl ShapeState {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let to_document = document.metadata().transform_to_document(*layer);
|
let to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
|
||||||
|
|
||||||
for &selected in &state.selected_points {
|
for &selected in &state.selected_points {
|
||||||
let source = match selected {
|
let source = match selected {
|
||||||
@@ -552,7 +564,11 @@ impl ShapeState {
|
|||||||
let already_selected = selected_shape_state.is_point_selected(manipulator_point_id);
|
let already_selected = selected_shape_state.is_point_selected(manipulator_point_id);
|
||||||
|
|
||||||
// Offset to snap the selected point to the cursor
|
// Offset to snap the selected point to the cursor
|
||||||
let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position);
|
let offset = mouse_position
|
||||||
|
- network_interface
|
||||||
|
.document_metadata()
|
||||||
|
.transform_to_viewport_if_feeds(layer, network_interface)
|
||||||
|
.transform_point2(point_position);
|
||||||
|
|
||||||
// This is selecting the manipulator only for now, next to generalize to points
|
// This is selecting the manipulator only for now, next to generalize to points
|
||||||
|
|
||||||
@@ -609,7 +625,11 @@ impl ShapeState {
|
|||||||
let already_selected = selected_shape_state.is_point_selected(manipulator_point_id);
|
let already_selected = selected_shape_state.is_point_selected(manipulator_point_id);
|
||||||
|
|
||||||
// Offset to snap the selected point to the cursor
|
// Offset to snap the selected point to the cursor
|
||||||
let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position);
|
let offset = mouse_position
|
||||||
|
- network_interface
|
||||||
|
.document_metadata()
|
||||||
|
.transform_to_viewport_if_feeds(layer, network_interface)
|
||||||
|
.transform_point2(point_position);
|
||||||
|
|
||||||
// Gather current selection information
|
// Gather current selection information
|
||||||
let points = self
|
let points = self
|
||||||
@@ -637,11 +657,11 @@ impl ShapeState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Selects all anchors connected to the selected subpath, and deselects all handles, for the given layer.
|
/// Selects all anchors connected to the selected subpath, and deselects all handles, for the given layer.
|
||||||
pub fn select_connected_anchors(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, mouse: DVec2) {
|
pub fn select_connected(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, mouse: DVec2, points: bool, segments: bool) {
|
||||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let to_viewport = document.metadata().transform_to_viewport(layer);
|
let to_viewport = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||||
let layer_mouse = to_viewport.inverse().transform_point2(mouse);
|
let layer_mouse = to_viewport.inverse().transform_point2(mouse);
|
||||||
let state = self.selected_shape_state.entry(layer).or_default();
|
let state = self.selected_shape_state.entry(layer).or_default();
|
||||||
|
|
||||||
@@ -655,18 +675,39 @@ impl ShapeState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.clear_points();
|
state.clear_points();
|
||||||
|
|
||||||
if selected_stack.is_empty() {
|
if selected_stack.is_empty() {
|
||||||
// Fall back on just selecting all points in the layer
|
// Fall back on just selecting all points/segments in the layer
|
||||||
for &point in vector_data.point_domain.ids() {
|
if points {
|
||||||
state.select_point(ManipulatorPointId::Anchor(point))
|
for &point in vector_data.point_domain.ids() {
|
||||||
|
state.select_point(ManipulatorPointId::Anchor(point));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
if segments {
|
||||||
// Select all connected points
|
for &segment in vector_data.segment_domain.ids() {
|
||||||
while let Some(point) = selected_stack.pop() {
|
state.select_segment(segment);
|
||||||
let anchor_point = ManipulatorPointId::Anchor(point);
|
}
|
||||||
if !state.is_point_selected(anchor_point) {
|
}
|
||||||
state.select_point(anchor_point);
|
return;
|
||||||
selected_stack.extend(vector_data.connected_points(point));
|
}
|
||||||
|
|
||||||
|
let mut connected_points = HashSet::new();
|
||||||
|
|
||||||
|
while let Some(point) = selected_stack.pop() {
|
||||||
|
if !connected_points.contains(&point) {
|
||||||
|
connected_points.insert(point);
|
||||||
|
selected_stack.extend(vector_data.connected_points(point));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if points {
|
||||||
|
connected_points.iter().for_each(|point| state.select_point(ManipulatorPointId::Anchor(*point)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if segments {
|
||||||
|
for (id, _, start, end) in vector_data.segment_bezier_iter() {
|
||||||
|
if connected_points.contains(&start) || connected_points.contains(&end) {
|
||||||
|
state.select_segment(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -842,7 +883,7 @@ impl ShapeState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let vector_data = network_interface.compute_modified_vector(layer)?;
|
let vector_data = network_interface.compute_modified_vector(layer)?;
|
||||||
let transform = network_interface.document_metadata().transform_to_document(layer).inverse();
|
let transform = network_interface.document_metadata().transform_to_document_if_feeds(layer, network_interface).inverse();
|
||||||
let position = transform.transform_point2(new_position);
|
let position = transform.transform_point2(new_position);
|
||||||
let current_position = point.get_position(&vector_data)?;
|
let current_position = point.get_position(&vector_data)?;
|
||||||
let delta = position - current_position;
|
let delta = position - current_position;
|
||||||
@@ -993,7 +1034,7 @@ impl ShapeState {
|
|||||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let transform = document.metadata().transform_to_document(layer);
|
let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
|
||||||
|
|
||||||
for &point in layer_state.selected_points.iter() {
|
for &point in layer_state.selected_points.iter() {
|
||||||
let Some(handles) = point.get_handle_pair(&vector_data) else { continue };
|
let Some(handles) = point.get_handle_pair(&vector_data) else { continue };
|
||||||
@@ -1038,7 +1079,7 @@ impl ShapeState {
|
|||||||
|
|
||||||
let mut normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize()));
|
let mut normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize()));
|
||||||
|
|
||||||
if normalized.is_none() {
|
if normalized.is_none() || handle_directions.iter().any(|&d| d.is_some_and(|d| d.length_squared() < f64::EPSILON * 1e5)) {
|
||||||
handle_directions = anchor_positions.map(|relative_anchor| relative_anchor.map(|relative_anchor| (relative_anchor - anchor) / 3.));
|
handle_directions = anchor_positions.map(|relative_anchor| relative_anchor.map(|relative_anchor| (relative_anchor - anchor) / 3.));
|
||||||
normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize()))
|
normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize()))
|
||||||
}
|
}
|
||||||
@@ -1083,8 +1124,8 @@ impl ShapeState {
|
|||||||
|
|
||||||
let opposing_handles = handle_lengths.as_ref().and_then(|handle_lengths| handle_lengths.get(&layer));
|
let opposing_handles = handle_lengths.as_ref().and_then(|handle_lengths| handle_lengths.get(&layer));
|
||||||
|
|
||||||
let transform_to_viewport_space = document.metadata().transform_to_viewport(layer);
|
let transform_to_viewport_space = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||||
let transform_to_document_space = document.metadata().transform_to_document(layer);
|
let transform_to_document_space = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
|
||||||
let delta_transform = if in_viewport_space {
|
let delta_transform = if in_viewport_space {
|
||||||
transform_to_viewport_space
|
transform_to_viewport_space
|
||||||
} else {
|
} else {
|
||||||
@@ -1177,7 +1218,7 @@ impl ShapeState {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(&layer, state)| {
|
.filter_map(|(&layer, state)| {
|
||||||
let vector_data = document.network_interface.compute_modified_vector(layer)?;
|
let vector_data = document.network_interface.compute_modified_vector(layer)?;
|
||||||
let transform = document.metadata().transform_to_document(layer);
|
let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
|
||||||
let opposing_handle_lengths = vector_data
|
let opposing_handle_lengths = vector_data
|
||||||
.colinear_manipulators
|
.colinear_manipulators
|
||||||
.iter()
|
.iter()
|
||||||
@@ -1542,7 +1583,7 @@ impl ShapeState {
|
|||||||
let mut manipulator_point = None;
|
let mut manipulator_point = None;
|
||||||
|
|
||||||
let vector_data = network_interface.compute_modified_vector(layer)?;
|
let vector_data = network_interface.compute_modified_vector(layer)?;
|
||||||
let viewspace = network_interface.document_metadata().transform_to_viewport(layer);
|
let viewspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||||
|
|
||||||
// Handles
|
// Handles
|
||||||
for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() {
|
for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() {
|
||||||
@@ -1578,7 +1619,7 @@ impl ShapeState {
|
|||||||
|
|
||||||
/// Find the `t` value along the path segment we have clicked upon, together with that segment ID.
|
/// Find the `t` value along the path segment we have clicked upon, together with that segment ID.
|
||||||
fn closest_segment(&self, network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<ClosestSegment> {
|
fn closest_segment(&self, network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<ClosestSegment> {
|
||||||
let transform = network_interface.document_metadata().transform_to_viewport(layer);
|
let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||||
let layer_pos = transform.inverse().transform_point2(position);
|
let layer_pos = transform.inverse().transform_point2(position);
|
||||||
|
|
||||||
let tolerance = tolerance + 0.5;
|
let tolerance = tolerance + 0.5;
|
||||||
@@ -1752,7 +1793,7 @@ impl ShapeState {
|
|||||||
pub fn flip_smooth_sharp(&self, network_interface: &NodeNetworkInterface, target: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
|
pub fn flip_smooth_sharp(&self, network_interface: &NodeNetworkInterface, target: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
|
||||||
let mut process_layer = |layer| {
|
let mut process_layer = |layer| {
|
||||||
let vector_data = network_interface.compute_modified_vector(layer)?;
|
let vector_data = network_interface.compute_modified_vector(layer)?;
|
||||||
let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport(layer);
|
let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||||
|
|
||||||
let mut result = None;
|
let mut result = None;
|
||||||
let mut closest_distance_squared = tolerance * tolerance;
|
let mut closest_distance_squared = tolerance * tolerance;
|
||||||
@@ -1856,7 +1897,7 @@ impl ShapeState {
|
|||||||
|
|
||||||
let vector_data = network_interface.compute_modified_vector(layer);
|
let vector_data = network_interface.compute_modified_vector(layer);
|
||||||
let Some(vector_data) = vector_data else { continue };
|
let Some(vector_data) = vector_data else { continue };
|
||||||
let transform = network_interface.document_metadata().transform_to_viewport(layer);
|
let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||||
|
|
||||||
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.start_point().count());
|
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.start_point().count());
|
||||||
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.end_point().count());
|
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.end_point().count());
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ mod test_ellipse {
|
|||||||
layers
|
layers
|
||||||
.filter_map(|layer| {
|
.filter_map(|layer| {
|
||||||
let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface);
|
let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface);
|
||||||
let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::protonode_identifier())?;
|
let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::IDENTIFIER)?;
|
||||||
Some(ResolvedEllipse {
|
Some(ResolvedEllipse {
|
||||||
radius_x: instrumented.grab_protonode_input::<ellipse::RadiusXInput>(&vec![ellipse_node], &editor.runtime).unwrap(),
|
radius_x: instrumented.grab_protonode_input::<ellipse::RadiusXInput>(&vec![ellipse_node], &editor.runtime).unwrap(),
|
||||||
radius_y: instrumented.grab_protonode_input::<ellipse::RadiusYInput>(&vec![ellipse_node], &editor.runtime).unwrap(),
|
radius_y: instrumented.grab_protonode_input::<ellipse::RadiusYInput>(&vec![ellipse_node], &editor.runtime).unwrap(),
|
||||||
|
|||||||
@@ -393,6 +393,7 @@ pub fn transforming_transform_cage(
|
|||||||
input: &InputPreprocessorMessageHandler,
|
input: &InputPreprocessorMessageHandler,
|
||||||
responses: &mut VecDeque<Message>,
|
responses: &mut VecDeque<Message>,
|
||||||
layers_dragging: &mut Vec<LayerNodeIdentifier>,
|
layers_dragging: &mut Vec<LayerNodeIdentifier>,
|
||||||
|
center_of_transformation: Option<DVec2>,
|
||||||
) -> (bool, bool, bool) {
|
) -> (bool, bool, bool) {
|
||||||
let dragging_bounds = bounding_box_manager.as_mut().and_then(|bounding_box| {
|
let dragging_bounds = bounding_box_manager.as_mut().and_then(|bounding_box| {
|
||||||
let edges = bounding_box.check_selected_edges(input.mouse.position);
|
let edges = bounding_box.check_selected_edges(input.mouse.position);
|
||||||
@@ -429,17 +430,12 @@ pub fn transforming_transform_cage(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut selected = Selected::new(
|
bounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| {
|
||||||
&mut bounds.original_transforms,
|
document
|
||||||
&mut bounds.center_of_transformation,
|
.network_interface
|
||||||
layers_dragging,
|
.selected_nodes()
|
||||||
responses,
|
.selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface)
|
||||||
&document.network_interface,
|
});
|
||||||
None,
|
|
||||||
&ToolType::Select,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
bounds.center_of_transformation = selected.mean_average_of_pivots();
|
|
||||||
|
|
||||||
// Check if we're hovering over a skew triangle
|
// Check if we're hovering over a skew triangle
|
||||||
let edges = bounds.check_selected_edges(input.mouse.position);
|
let edges = bounds.check_selected_edges(input.mouse.position);
|
||||||
@@ -469,18 +465,12 @@ pub fn transforming_transform_cage(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut selected = Selected::new(
|
bounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| {
|
||||||
&mut bounds.original_transforms,
|
document
|
||||||
&mut bounds.center_of_transformation,
|
.network_interface
|
||||||
&selected,
|
.selected_nodes()
|
||||||
responses,
|
.selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface)
|
||||||
&document.network_interface,
|
});
|
||||||
None,
|
|
||||||
&ToolType::Select,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
bounds.center_of_transformation = selected.mean_average_of_pivots();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*layers_dragging = selected;
|
*layers_dragging = selected;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use graphene_std::raster::color::Color;
|
|||||||
|
|
||||||
const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays(context).into();
|
const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays(context).into();
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
pub struct ToolMessageData<'a> {
|
pub struct ToolMessageData<'a> {
|
||||||
pub document_id: DocumentId,
|
pub document_id: DocumentId,
|
||||||
pub document: &'a mut DocumentMessageHandler,
|
pub document: &'a mut DocumentMessageHandler,
|
||||||
@@ -21,7 +22,7 @@ pub struct ToolMessageData<'a> {
|
|||||||
pub preferences: &'a PreferencesMessageHandler,
|
pub preferences: &'a PreferencesMessageHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, ExtractField)]
|
||||||
pub struct ToolMessageHandler {
|
pub struct ToolMessageHandler {
|
||||||
pub tool_state: ToolFsmState,
|
pub tool_state: ToolFsmState,
|
||||||
pub transform_layer_handler: TransformLayerMessageHandler,
|
pub transform_layer_handler: TransformLayerMessageHandler,
|
||||||
@@ -29,6 +30,7 @@ pub struct ToolMessageHandler {
|
|||||||
pub tool_is_active: bool,
|
pub tool_is_active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, data: ToolMessageData) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, data: ToolMessageData) {
|
||||||
let ToolMessageData {
|
let ToolMessageData {
|
||||||
@@ -181,6 +183,11 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
|||||||
send: Box::new(TransformLayerMessage::SelectionChanged.into()),
|
send: Box::new(TransformLayerMessage::SelectionChanged.into()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
responses.add(BroadcastMessage::SubscribeEvent {
|
||||||
|
on: BroadcastEvent::SelectionChanged,
|
||||||
|
send: Box::new(SelectToolMessage::SyncHistory.into()),
|
||||||
|
});
|
||||||
|
|
||||||
self.tool_is_active = true;
|
self.tool_is_active = true;
|
||||||
|
|
||||||
let tool_data = &mut self.tool_state.tool_data;
|
let tool_data = &mut self.tool_state.tool_data;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::messages::tool::common_functionality::transformation_cage::*;
|
|||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct ArtboardTool {
|
pub struct ArtboardTool {
|
||||||
fsm_state: ArtboardToolFsmState,
|
fsm_state: ArtboardToolFsmState,
|
||||||
data: ArtboardToolData,
|
data: ArtboardToolData,
|
||||||
@@ -48,6 +48,7 @@ impl ToolMetadata for ArtboardTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ArtboardTool {
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ArtboardTool {
|
||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, false);
|
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, false);
|
||||||
@@ -567,7 +568,7 @@ mod test_artboard {
|
|||||||
Ok(instrumented) => instrumented,
|
Ok(instrumented) => instrumented,
|
||||||
Err(e) => panic!("Failed to evaluate graph: {}", e),
|
Err(e) => panic!("Failed to evaluate graph: {}", e),
|
||||||
};
|
};
|
||||||
instrumented.grab_all_input::<graphene_std::append_artboard::ArtboardInput>(&editor.runtime).collect()
|
instrumented.grab_all_input::<graphene_std::graphic_element::append_artboard::ArtboardInput>(&editor.runtime).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::consts::DEFAULT_BRUSH_SIZE;
|
use crate::consts::DEFAULT_BRUSH_SIZE;
|
||||||
use crate::messages::portfolio::document::graph_operation::transform_utils::{get_current_normalized_pivot, get_current_transform};
|
use crate::messages::portfolio::document::graph_operation::transform_utils::get_current_transform;
|
||||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
|
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
|
||||||
@@ -20,7 +20,7 @@ pub enum DrawMode {
|
|||||||
Restore,
|
Restore,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct BrushTool {
|
pub struct BrushTool {
|
||||||
fsm_state: BrushToolFsmState,
|
fsm_state: BrushToolFsmState,
|
||||||
data: BrushToolData,
|
data: BrushToolData,
|
||||||
@@ -185,6 +185,7 @@ impl LayoutHolder for BrushTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTool {
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTool {
|
||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||||
let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else {
|
let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else {
|
||||||
@@ -286,9 +287,7 @@ impl BrushToolData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if *reference == Some("Transform".to_string()) {
|
if *reference == Some("Transform".to_string()) {
|
||||||
let upstream = document.metadata().upstream_transform(node_id);
|
self.transform = get_current_transform(&node.inputs) * self.transform;
|
||||||
let pivot = DAffine2::from_translation(upstream.transform_point2(get_current_normalized_pivot(&node.inputs)));
|
|
||||||
self.transform = pivot * get_current_transform(&node.inputs) * pivot.inverse() * self.transform;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::messages::tool::utility_types::DocumentToolData;
|
use crate::messages::tool::utility_types::DocumentToolData;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct EyedropperTool {
|
pub struct EyedropperTool {
|
||||||
fsm_state: EyedropperToolFsmState,
|
fsm_state: EyedropperToolFsmState,
|
||||||
data: EyedropperToolData,
|
data: EyedropperToolData,
|
||||||
@@ -39,6 +39,7 @@ impl LayoutHolder for EyedropperTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EyedropperTool {
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EyedropperTool {
|
||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
|
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContex
|
|||||||
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
|
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
|
||||||
use graphene_std::vector::style::Fill;
|
use graphene_std::vector::style::Fill;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct FillTool {
|
pub struct FillTool {
|
||||||
fsm_state: FillToolFsmState,
|
fsm_state: FillToolFsmState,
|
||||||
}
|
}
|
||||||
@@ -41,6 +41,7 @@ impl LayoutHolder for FillTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillTool {
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillTool {
|
||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||||
self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true);
|
self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use graphene_std::Color;
|
|||||||
use graphene_std::vector::VectorModificationType;
|
use graphene_std::vector::VectorModificationType;
|
||||||
use graphene_std::vector::{PointId, SegmentId};
|
use graphene_std::vector::{PointId, SegmentId};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct FreehandTool {
|
pub struct FreehandTool {
|
||||||
fsm_state: FreehandToolFsmState,
|
fsm_state: FreehandToolFsmState,
|
||||||
data: FreehandToolData,
|
data: FreehandToolData,
|
||||||
@@ -116,6 +116,7 @@ impl LayoutHolder for FreehandTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FreehandTool {
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FreehandTool {
|
||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||||
let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else {
|
let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{Node
|
|||||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||||
use graphene_std::vector::style::{Fill, Gradient, GradientType};
|
use graphene_std::vector::style::{Fill, Gradient, GradientType};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct GradientTool {
|
pub struct GradientTool {
|
||||||
fsm_state: GradientToolFsmState,
|
fsm_state: GradientToolFsmState,
|
||||||
data: GradientToolData,
|
data: GradientToolData,
|
||||||
@@ -53,6 +53,7 @@ impl ToolMetadata for GradientTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for GradientTool {
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for GradientTool {
|
||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||||
let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else {
|
let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct NavigateTool {
|
pub struct NavigateTool {
|
||||||
fsm_state: NavigateToolFsmState,
|
fsm_state: NavigateToolFsmState,
|
||||||
tool_data: NavigateToolData,
|
tool_data: NavigateToolData,
|
||||||
@@ -38,6 +38,7 @@ impl LayoutHolder for NavigateTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for NavigateTool {
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for NavigateTool {
|
||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use super::select_tool::extend_lasso;
|
use super::select_tool::extend_lasso;
|
||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::consts::{
|
use crate::consts::{
|
||||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, SEGMENT_INSERTION_DISTANCE,
|
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE,
|
||||||
SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE,
|
SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE,
|
||||||
};
|
};
|
||||||
use crate::messages::portfolio::document::overlays::utility_functions::{path_overlays, selected_segments};
|
use crate::messages::portfolio::document::overlays::utility_functions::{path_overlays, selected_segments};
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext};
|
use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext};
|
||||||
@@ -11,18 +11,20 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node
|
|||||||
use crate::messages::portfolio::document::utility_types::transformation::Axis;
|
use crate::messages::portfolio::document::utility_types::transformation::Axis;
|
||||||
use crate::messages::preferences::SelectionMode;
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
|
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget};
|
||||||
use crate::messages::tool::common_functionality::shape_editor::{
|
use crate::messages::tool::common_functionality::shape_editor::{
|
||||||
ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState,
|
ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState,
|
||||||
};
|
};
|
||||||
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
||||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate};
|
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate};
|
||||||
use bezier_rs::{Bezier, TValue};
|
use bezier_rs::{Bezier, BezierHandles, TValue};
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
|
use graphene_std::transform::ReferencePoint;
|
||||||
use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData};
|
use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData};
|
||||||
use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType};
|
use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType};
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct PathTool {
|
pub struct PathTool {
|
||||||
fsm_state: PathToolFsmState,
|
fsm_state: PathToolFsmState,
|
||||||
tool_data: PathToolData,
|
tool_data: PathToolData,
|
||||||
@@ -58,7 +60,10 @@ pub enum PathToolMessage {
|
|||||||
},
|
},
|
||||||
Escape,
|
Escape,
|
||||||
ClosePath,
|
ClosePath,
|
||||||
FlipSmoothSharp,
|
DoubleClick {
|
||||||
|
extend_selection: Key,
|
||||||
|
shrink_selection: Key,
|
||||||
|
},
|
||||||
GRS {
|
GRS {
|
||||||
// Should be `Key::KeyG` (Grab), `Key::KeyR` (Rotate), or `Key::KeyS` (Scale)
|
// Should be `Key::KeyG` (Grab), `Key::KeyR` (Rotate), or `Key::KeyS` (Scale)
|
||||||
key: Key,
|
key: Key,
|
||||||
@@ -103,6 +108,9 @@ pub enum PathToolMessage {
|
|||||||
SelectedPointYChanged {
|
SelectedPointYChanged {
|
||||||
new_y: f64,
|
new_y: f64,
|
||||||
},
|
},
|
||||||
|
SetPivot {
|
||||||
|
position: ReferencePoint,
|
||||||
|
},
|
||||||
SwapSelectedHandles,
|
SwapSelectedHandles,
|
||||||
UpdateOptions(PathOptionsUpdate),
|
UpdateOptions(PathOptionsUpdate),
|
||||||
UpdateSelectedPointsStatus {
|
UpdateSelectedPointsStatus {
|
||||||
@@ -138,6 +146,9 @@ pub enum PathOptionsUpdate {
|
|||||||
OverlayModeType(PathOverlayMode),
|
OverlayModeType(PathOverlayMode),
|
||||||
PointEditingMode { enabled: bool },
|
PointEditingMode { enabled: bool },
|
||||||
SegmentEditingMode { enabled: bool },
|
SegmentEditingMode { enabled: bool },
|
||||||
|
PivotGizmoType(PivotGizmoType),
|
||||||
|
TogglePivotGizmoType(bool),
|
||||||
|
TogglePivotPinned,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToolMetadata for PathTool {
|
impl ToolMetadata for PathTool {
|
||||||
@@ -252,6 +263,20 @@ impl LayoutHolder for PathTool {
|
|||||||
.selected_index(Some(self.options.path_overlay_mode as u32))
|
.selected_index(Some(self.options.path_overlay_mode as u32))
|
||||||
.widget_holder();
|
.widget_holder();
|
||||||
|
|
||||||
|
let [_checkbox, _dropdown] = {
|
||||||
|
let pivot_gizmo_type_widget = pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Path);
|
||||||
|
[pivot_gizmo_type_widget[0].clone(), pivot_gizmo_type_widget[2].clone()]
|
||||||
|
};
|
||||||
|
|
||||||
|
let has_something = !self.tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty();
|
||||||
|
let _pivot_reference = pivot_reference_point_widget(
|
||||||
|
has_something || !self.tool_data.pivot_gizmo.state.is_pivot(),
|
||||||
|
self.tool_data.pivot_gizmo.pivot.to_pivot_position(),
|
||||||
|
PivotToolSource::Path,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _pin_pivot = pin_pivot_widget(self.tool_data.pivot_gizmo.pin_active(), false, PivotToolSource::Path);
|
||||||
|
|
||||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
|
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
x_location,
|
x_location,
|
||||||
@@ -265,13 +290,22 @@ impl LayoutHolder for PathTool {
|
|||||||
point_editing_mode,
|
point_editing_mode,
|
||||||
related_seperator.clone(),
|
related_seperator.clone(),
|
||||||
segment_editing_mode,
|
segment_editing_mode,
|
||||||
unrelated_seperator,
|
unrelated_seperator.clone(),
|
||||||
path_overlay_mode_widget,
|
path_overlay_mode_widget,
|
||||||
|
unrelated_seperator.clone(),
|
||||||
|
// checkbox.clone(),
|
||||||
|
// related_seperator.clone(),
|
||||||
|
// dropdown.clone(),
|
||||||
|
// unrelated_seperator,
|
||||||
|
// pivot_reference,
|
||||||
|
// related_seperator.clone(),
|
||||||
|
// pin_pivot,
|
||||||
],
|
],
|
||||||
}]))
|
}]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathTool {
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathTool {
|
||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||||
let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated);
|
let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated);
|
||||||
@@ -290,6 +324,29 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
|||||||
self.options.path_editing_mode.segment_editing_mode = enabled;
|
self.options.path_editing_mode.segment_editing_mode = enabled;
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
}
|
}
|
||||||
|
PathOptionsUpdate::PivotGizmoType(gizmo_type) => {
|
||||||
|
if !self.tool_data.pivot_gizmo.state.disabled {
|
||||||
|
self.tool_data.pivot_gizmo.state.gizmo_type = gizmo_type;
|
||||||
|
responses.add(ToolMessage::UpdateHints);
|
||||||
|
let pivot_gizmo = self.tool_data.pivot_gizmo();
|
||||||
|
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PathOptionsUpdate::TogglePivotGizmoType(state) => {
|
||||||
|
self.tool_data.pivot_gizmo.state.disabled = !state;
|
||||||
|
responses.add(ToolMessage::UpdateHints);
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
PathOptionsUpdate::TogglePivotPinned => {
|
||||||
|
self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;
|
||||||
|
responses.add(ToolMessage::UpdateHints);
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ToolMessage::Path(PathToolMessage::ClosePath) => {
|
ToolMessage::Path(PathToolMessage::ClosePath) => {
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
@@ -319,7 +376,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
|||||||
fn actions(&self) -> ActionList {
|
fn actions(&self) -> ActionList {
|
||||||
match self.fsm_state {
|
match self.fsm_state {
|
||||||
PathToolFsmState::Ready => actions!(PathToolMessageDiscriminant;
|
PathToolFsmState::Ready => actions!(PathToolMessageDiscriminant;
|
||||||
FlipSmoothSharp,
|
DoubleClick,
|
||||||
MouseDown,
|
MouseDown,
|
||||||
Delete,
|
Delete,
|
||||||
NudgeSelectedPoints,
|
NudgeSelectedPoints,
|
||||||
@@ -334,7 +391,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
|||||||
PathToolFsmState::Dragging(_) => actions!(PathToolMessageDiscriminant;
|
PathToolFsmState::Dragging(_) => actions!(PathToolMessageDiscriminant;
|
||||||
Escape,
|
Escape,
|
||||||
RightClick,
|
RightClick,
|
||||||
FlipSmoothSharp,
|
DoubleClick,
|
||||||
DragStop,
|
DragStop,
|
||||||
PointerMove,
|
PointerMove,
|
||||||
Delete,
|
Delete,
|
||||||
@@ -343,7 +400,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
|||||||
SwapSelectedHandles,
|
SwapSelectedHandles,
|
||||||
),
|
),
|
||||||
PathToolFsmState::Drawing { .. } => actions!(PathToolMessageDiscriminant;
|
PathToolFsmState::Drawing { .. } => actions!(PathToolMessageDiscriminant;
|
||||||
FlipSmoothSharp,
|
DoubleClick,
|
||||||
DragStop,
|
DragStop,
|
||||||
PointerMove,
|
PointerMove,
|
||||||
Delete,
|
Delete,
|
||||||
@@ -359,12 +416,6 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
|||||||
Escape,
|
Escape,
|
||||||
RightClick
|
RightClick
|
||||||
),
|
),
|
||||||
PathToolFsmState::MoldingSegment => actions!(PathToolMessageDiscriminant;
|
|
||||||
PointerMove,
|
|
||||||
DragStop,
|
|
||||||
RightClick,
|
|
||||||
Escape,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,7 +467,6 @@ enum PathToolFsmState {
|
|||||||
selection_shape: SelectionShapeType,
|
selection_shape: SelectionShapeType,
|
||||||
},
|
},
|
||||||
SlidingPoint,
|
SlidingPoint,
|
||||||
MoldingSegment,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -447,6 +497,8 @@ struct PathToolData {
|
|||||||
last_click_time: u64,
|
last_click_time: u64,
|
||||||
dragging_state: DraggingState,
|
dragging_state: DraggingState,
|
||||||
angle: f64,
|
angle: f64,
|
||||||
|
pivot_gizmo: PivotGizmo,
|
||||||
|
ordered_points: Vec<ManipulatorPointId>,
|
||||||
opposite_handle_position: Option<DVec2>,
|
opposite_handle_position: Option<DVec2>,
|
||||||
last_clicked_point_was_selected: bool,
|
last_clicked_point_was_selected: bool,
|
||||||
last_clicked_segment_was_selected: bool,
|
last_clicked_segment_was_selected: bool,
|
||||||
@@ -462,6 +514,8 @@ struct PathToolData {
|
|||||||
adjacent_anchor_offset: Option<DVec2>,
|
adjacent_anchor_offset: Option<DVec2>,
|
||||||
sliding_point_info: Option<SlidingPointInfo>,
|
sliding_point_info: Option<SlidingPointInfo>,
|
||||||
started_drawing_from_inside: bool,
|
started_drawing_from_inside: bool,
|
||||||
|
first_selected_with_single_click: bool,
|
||||||
|
stored_selection: Option<HashMap<LayerNodeIdentifier, SelectedLayerState>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PathToolData {
|
impl PathToolData {
|
||||||
@@ -544,8 +598,9 @@ impl PathToolData {
|
|||||||
|
|
||||||
self.drag_start_pos = input.mouse.position;
|
self.drag_start_pos = input.mouse.position;
|
||||||
|
|
||||||
if !self.saved_points_before_anchor_convert_smooth_sharp.is_empty() && (input.time - self.last_click_time > 500) {
|
if input.time - self.last_click_time > DOUBLE_CLICK_MILLISECONDS {
|
||||||
self.saved_points_before_anchor_convert_smooth_sharp.clear();
|
self.saved_points_before_anchor_convert_smooth_sharp.clear();
|
||||||
|
self.stored_selection = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.last_click_time = input.time;
|
self.last_click_time = input.time;
|
||||||
@@ -675,30 +730,30 @@ impl PathToolData {
|
|||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
PathToolFsmState::Dragging(self.dragging_state)
|
PathToolFsmState::Dragging(self.dragging_state)
|
||||||
} else {
|
} else {
|
||||||
let handle1 = ManipulatorPointId::PrimaryHandle(segment.segment());
|
let start_pos = segment.bezier().start;
|
||||||
let handle2 = ManipulatorPointId::EndHandle(segment.segment());
|
let end_pos = segment.bezier().end;
|
||||||
if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) {
|
|
||||||
if let (Some(pos1), Some(pos2)) = (handle1.get_position(&vector_data), handle2.get_position(&vector_data)) {
|
let [pos1, pos2] = match segment.bezier().handles {
|
||||||
self.molding_info = Some((pos1, pos2))
|
BezierHandles::Cubic { handle_start, handle_end } => [handle_start, handle_end],
|
||||||
}
|
BezierHandles::Quadratic { handle } => [handle, end_pos],
|
||||||
}
|
BezierHandles::Linear => [start_pos + (end_pos - start_pos) / 3., end_pos + (start_pos - end_pos) / 3.],
|
||||||
PathToolFsmState::MoldingSegment
|
};
|
||||||
|
self.molding_info = Some((pos1, pos2));
|
||||||
|
PathToolFsmState::Dragging(self.dragging_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We didn't find a segment, so consider selecting the nearest shape instead and start drawing
|
// If no other layers are selected and this is a single-click, then also select the layer (exception)
|
||||||
else if let Some(layer) = document.click(input) {
|
else if let Some(layer) = document.click(input) {
|
||||||
shape_editor.deselect_all_points();
|
if shape_editor.selected_shape_state.is_empty() {
|
||||||
shape_editor.deselect_all_segments();
|
self.first_selected_with_single_click = true;
|
||||||
if extend_selection {
|
|
||||||
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
|
|
||||||
} else {
|
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
|
||||||
}
|
}
|
||||||
self.drag_start_pos = input.mouse.position;
|
|
||||||
self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
|
|
||||||
|
|
||||||
self.started_drawing_from_inside = true;
|
self.started_drawing_from_inside = true;
|
||||||
|
|
||||||
|
self.drag_start_pos = input.mouse.position;
|
||||||
|
self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
|
||||||
|
|
||||||
let selection_shape = if lasso_select { SelectionShapeType::Lasso } else { SelectionShapeType::Box };
|
let selection_shape = if lasso_select { SelectionShapeType::Lasso } else { SelectionShapeType::Box };
|
||||||
PathToolFsmState::Drawing { selection_shape }
|
PathToolFsmState::Drawing { selection_shape }
|
||||||
}
|
}
|
||||||
@@ -719,7 +774,7 @@ impl PathToolData {
|
|||||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let transform = document.metadata().transform_to_document(layer);
|
let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
|
||||||
|
|
||||||
let mut layer_manipulators = HashSet::with_hasher(NoHashBuilder);
|
let mut layer_manipulators = HashSet::with_hasher(NoHashBuilder);
|
||||||
for point in state.selected_points() {
|
for point in state.selected_points() {
|
||||||
@@ -829,7 +884,7 @@ impl PathToolData {
|
|||||||
let selected_handle = selection.selected_points().next()?.as_handle()?;
|
let selected_handle = selection.selected_points().next()?.as_handle()?;
|
||||||
let handle_id = selected_handle.to_manipulator_point();
|
let handle_id = selected_handle.to_manipulator_point();
|
||||||
|
|
||||||
let layer_to_document = document.metadata().transform_to_document(*layer);
|
let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
|
||||||
let vector_data = document.network_interface.compute_modified_vector(*layer)?;
|
let vector_data = document.network_interface.compute_modified_vector(*layer)?;
|
||||||
|
|
||||||
let handle_position_local = selected_handle.to_manipulator_point().get_position(&vector_data)?;
|
let handle_position_local = selected_handle.to_manipulator_point().get_position(&vector_data)?;
|
||||||
@@ -871,7 +926,7 @@ impl PathToolData {
|
|||||||
let anchor = handle_id.get_anchor(&vector_data);
|
let anchor = handle_id.get_anchor(&vector_data);
|
||||||
let (angle, anchor_position) = calculate_adjacent_anchor_tangent(handle_id, anchor, adjacent_anchor, &vector_data);
|
let (angle, anchor_position) = calculate_adjacent_anchor_tangent(handle_id, anchor, adjacent_anchor, &vector_data);
|
||||||
|
|
||||||
let layer_to_document = document.metadata().transform_to_document(*layer);
|
let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
|
||||||
|
|
||||||
self.adjacent_anchor_offset = handle_id
|
self.adjacent_anchor_offset = handle_id
|
||||||
.get_anchor_position(&vector_data)
|
.get_anchor_position(&vector_data)
|
||||||
@@ -1018,7 +1073,7 @@ impl PathToolData {
|
|||||||
}
|
}
|
||||||
// If already hovering on a segment, then recalculate its closest point
|
// If already hovering on a segment, then recalculate its closest point
|
||||||
else if let Some(closest_segment) = &mut self.segment {
|
else if let Some(closest_segment) = &mut self.segment {
|
||||||
closest_segment.update_closest_point(document.metadata(), position);
|
closest_segment.update_closest_point(document.metadata(), &document.network_interface, position);
|
||||||
|
|
||||||
if closest_segment.too_far(position, SEGMENT_INSERTION_DISTANCE) {
|
if closest_segment.too_far(position, SEGMENT_INSERTION_DISTANCE) {
|
||||||
self.segment = None;
|
self.segment = None;
|
||||||
@@ -1085,7 +1140,7 @@ impl PathToolData {
|
|||||||
let layer = sliding_point_info.layer;
|
let layer = sliding_point_info.layer;
|
||||||
|
|
||||||
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { return };
|
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { return };
|
||||||
let transform = network_interface.document_metadata().transform_to_viewport(layer);
|
let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||||
let layer_pos = transform.inverse().transform_point2(target_position);
|
let layer_pos = transform.inverse().transform_point2(target_position);
|
||||||
|
|
||||||
let segments = sliding_point_info.connected_segments;
|
let segments = sliding_point_info.connected_segments;
|
||||||
@@ -1316,6 +1371,16 @@ impl PathToolData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pivot_gizmo(&self) -> PivotGizmo {
|
||||||
|
self.pivot_gizmo.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_history(&mut self, points: &[ManipulatorPointId]) {
|
||||||
|
self.ordered_points.retain(|layer| points.contains(layer));
|
||||||
|
self.ordered_points.extend(points.iter().find(|&layer| !self.ordered_points.contains(layer)));
|
||||||
|
self.pivot_gizmo.point = self.ordered_points.last().copied()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fsm for PathToolFsmState {
|
impl Fsm for PathToolFsmState {
|
||||||
@@ -1328,6 +1393,10 @@ impl Fsm for PathToolFsmState {
|
|||||||
update_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options);
|
update_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options);
|
||||||
|
|
||||||
let ToolMessage::Path(event) = event else { return self };
|
let ToolMessage::Path(event) = event else { return self };
|
||||||
|
|
||||||
|
// TODO(mTvare6): Remove once gizmos are implemented for path_tool
|
||||||
|
tool_data.pivot_gizmo.state.disabled = true;
|
||||||
|
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(_, PathToolMessage::SelectionChanged) => {
|
(_, PathToolMessage::SelectionChanged) => {
|
||||||
// Set the newly targeted layers to visible
|
// Set the newly targeted layers to visible
|
||||||
@@ -1344,6 +1413,9 @@ impl Fsm for PathToolFsmState {
|
|||||||
shape_editor.update_selected_anchors_status(display_anchors);
|
shape_editor.update_selected_anchors_status(display_anchors);
|
||||||
shape_editor.update_selected_handles_status(display_handles);
|
shape_editor.update_selected_handles_status(display_handles);
|
||||||
|
|
||||||
|
let new_points = shape_editor.selected_points().copied().collect::<Vec<_>>();
|
||||||
|
tool_data.sync_history(&new_points);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::Overlays(mut overlay_context)) => {
|
(_, PathToolMessage::Overlays(mut overlay_context)) => {
|
||||||
@@ -1413,7 +1485,7 @@ impl Fsm for PathToolFsmState {
|
|||||||
|
|
||||||
if let Some(closest_segment) = &tool_data.segment {
|
if let Some(closest_segment) = &tool_data.segment {
|
||||||
if tool_options.path_editing_mode.segment_editing_mode {
|
if tool_options.path_editing_mode.segment_editing_mode {
|
||||||
let transform = document.metadata().transform_to_viewport(closest_segment.layer());
|
let transform = document.metadata().transform_to_viewport_if_feeds(closest_segment.layer(), &document.network_interface);
|
||||||
|
|
||||||
overlay_context.outline_overlay_bezier(closest_segment.bezier(), transform);
|
overlay_context.outline_overlay_bezier(closest_segment.bezier(), transform);
|
||||||
|
|
||||||
@@ -1431,7 +1503,7 @@ impl Fsm for PathToolFsmState {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let perp = closest_segment.calculate_perp(document);
|
let perp = closest_segment.calculate_perp(document);
|
||||||
let point = closest_segment.closest_point(document.metadata());
|
let point = closest_segment.closest_point(document.metadata(), &document.network_interface);
|
||||||
|
|
||||||
// Draw an X on the segment
|
// Draw an X on the segment
|
||||||
if tool_data.delete_segment_pressed {
|
if tool_data.delete_segment_pressed {
|
||||||
@@ -1501,7 +1573,6 @@ impl Fsm for PathToolFsmState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::SlidingPoint => {}
|
Self::SlidingPoint => {}
|
||||||
Self::MoldingSegment => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||||
@@ -1557,7 +1628,9 @@ impl Fsm for PathToolFsmState {
|
|||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
tool_data.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
|
tool_data.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
|
||||||
|
|
||||||
tool_data.started_drawing_from_inside = false;
|
tool_data.started_drawing_from_inside = false;
|
||||||
|
tool_data.stored_selection = None;
|
||||||
|
|
||||||
if selection_shape == SelectionShapeType::Lasso {
|
if selection_shape == SelectionShapeType::Lasso {
|
||||||
extend_lasso(&mut tool_data.lasso_polygon, input.mouse.position);
|
extend_lasso(&mut tool_data.lasso_polygon, input.mouse.position);
|
||||||
@@ -1604,21 +1677,35 @@ impl Fsm for PathToolFsmState {
|
|||||||
break_colinear_molding,
|
break_colinear_molding,
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
let mut selected_only_handles = true;
|
let selected_only_handles = !shape_editor.selected_points().any(|point| matches!(point, ManipulatorPointId::Anchor(_)));
|
||||||
|
tool_data.stored_selection = None;
|
||||||
let selected_points = shape_editor.selected_points();
|
|
||||||
|
|
||||||
for point in selected_points {
|
|
||||||
if matches!(point, ManipulatorPointId::Anchor(_)) {
|
|
||||||
selected_only_handles = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !tool_data.saved_points_before_handle_drag.is_empty() && (tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD) && (selected_only_handles) {
|
if !tool_data.saved_points_before_handle_drag.is_empty() && (tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD) && (selected_only_handles) {
|
||||||
tool_data.handle_drag_toggle = true;
|
tool_data.handle_drag_toggle = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {
|
||||||
|
tool_data.molding_segment = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let break_molding = input.keyboard.get(break_colinear_molding as usize);
|
||||||
|
|
||||||
|
// Logic for molding segment
|
||||||
|
if let Some(segment) = &mut tool_data.segment {
|
||||||
|
if let Some(molding_segment_handles) = tool_data.molding_info {
|
||||||
|
tool_data.temporary_adjacent_handles_while_molding = segment.mold_handle_positions(
|
||||||
|
document,
|
||||||
|
responses,
|
||||||
|
molding_segment_handles,
|
||||||
|
input.mouse.position,
|
||||||
|
break_molding,
|
||||||
|
tool_data.temporary_adjacent_handles_while_molding,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PathToolFsmState::Dragging(tool_data.dragging_state);
|
||||||
|
}
|
||||||
|
|
||||||
let anchor_and_handle_toggled = input.keyboard.get(move_anchor_with_handles as usize);
|
let anchor_and_handle_toggled = input.keyboard.get(move_anchor_with_handles as usize);
|
||||||
let initial_press = anchor_and_handle_toggled && !tool_data.select_anchor_toggled;
|
let initial_press = anchor_and_handle_toggled && !tool_data.select_anchor_toggled;
|
||||||
let released_from_toggle = tool_data.select_anchor_toggled && !anchor_and_handle_toggled;
|
let released_from_toggle = tool_data.select_anchor_toggled && !anchor_and_handle_toggled;
|
||||||
@@ -1694,39 +1781,11 @@ impl Fsm for PathToolFsmState {
|
|||||||
tool_data.slide_point(input.mouse.position, responses, &document.network_interface, shape_editor);
|
tool_data.slide_point(input.mouse.position, responses, &document.network_interface, shape_editor);
|
||||||
PathToolFsmState::SlidingPoint
|
PathToolFsmState::SlidingPoint
|
||||||
}
|
}
|
||||||
(PathToolFsmState::MoldingSegment, PathToolMessage::PointerMove { break_colinear_molding, .. }) => {
|
|
||||||
if tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {
|
|
||||||
tool_data.molding_segment = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let break_colinear_molding = input.keyboard.get(break_colinear_molding as usize);
|
|
||||||
|
|
||||||
// Logic for molding segment
|
|
||||||
if let Some(segment) = &mut tool_data.segment {
|
|
||||||
if let Some(molding_segment_handles) = tool_data.molding_info {
|
|
||||||
tool_data.temporary_adjacent_handles_while_molding = segment.mold_handle_positions(
|
|
||||||
document,
|
|
||||||
responses,
|
|
||||||
molding_segment_handles,
|
|
||||||
input.mouse.position,
|
|
||||||
break_colinear_molding,
|
|
||||||
tool_data.temporary_adjacent_handles_while_molding,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PathToolFsmState::MoldingSegment
|
|
||||||
}
|
|
||||||
(PathToolFsmState::Ready, PathToolMessage::PointerMove { delete_segment, .. }) => {
|
(PathToolFsmState::Ready, PathToolMessage::PointerMove { delete_segment, .. }) => {
|
||||||
tool_data.delete_segment_pressed = input.keyboard.get(delete_segment as usize);
|
tool_data.delete_segment_pressed = input.keyboard.get(delete_segment as usize);
|
||||||
|
tool_data.saved_points_before_anchor_convert_smooth_sharp.clear();
|
||||||
if !tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty() {
|
tool_data.adjacent_anchor_offset = None;
|
||||||
tool_data.saved_points_before_anchor_convert_smooth_sharp.clear();
|
tool_data.stored_selection = None;
|
||||||
}
|
|
||||||
|
|
||||||
if tool_data.adjacent_anchor_offset.is_some() {
|
|
||||||
tool_data.adjacent_anchor_offset = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
@@ -1847,6 +1906,9 @@ impl Fsm for PathToolFsmState {
|
|||||||
tool_data.saved_points_before_handle_drag.clear();
|
tool_data.saved_points_before_handle_drag.clear();
|
||||||
tool_data.handle_drag_toggle = false;
|
tool_data.handle_drag_toggle = false;
|
||||||
}
|
}
|
||||||
|
tool_data.molding_info = None;
|
||||||
|
tool_data.molding_segment = false;
|
||||||
|
tool_data.temporary_adjacent_handles_while_molding = None;
|
||||||
tool_data.angle_locked = false;
|
tool_data.angle_locked = false;
|
||||||
responses.add(DocumentMessage::AbortTransaction);
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
@@ -1864,17 +1926,6 @@ impl Fsm for PathToolFsmState {
|
|||||||
|
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(PathToolFsmState::MoldingSegment, PathToolMessage::Escape | PathToolMessage::RightClick) => {
|
|
||||||
// Undo the molding and go back to the state before
|
|
||||||
tool_data.molding_info = None;
|
|
||||||
tool_data.molding_segment = false;
|
|
||||||
tool_data.temporary_adjacent_handles_while_molding = None;
|
|
||||||
|
|
||||||
responses.add(DocumentMessage::AbortTransaction);
|
|
||||||
tool_data.snap_manager.cleanup(responses);
|
|
||||||
|
|
||||||
PathToolFsmState::Ready
|
|
||||||
}
|
|
||||||
// Mouse up
|
// Mouse up
|
||||||
(PathToolFsmState::Drawing { selection_shape }, PathToolMessage::DragStop { extend_selection, shrink_selection }) => {
|
(PathToolFsmState::Drawing { selection_shape }, PathToolMessage::DragStop { extend_selection, shrink_selection }) => {
|
||||||
let extend_selection = input.keyboard.get(extend_selection as usize);
|
let extend_selection = input.keyboard.get(extend_selection as usize);
|
||||||
@@ -1895,12 +1946,16 @@ impl Fsm for PathToolFsmState {
|
|||||||
SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(document.metadata()),
|
SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(document.metadata()),
|
||||||
selection_mode => selection_mode,
|
selection_mode => selection_mode,
|
||||||
};
|
};
|
||||||
|
tool_data.started_drawing_from_inside = false;
|
||||||
|
|
||||||
if tool_data.drag_start_pos.distance(previous_mouse) < 1e-8 {
|
if tool_data.drag_start_pos.distance(previous_mouse) < 1e-8 {
|
||||||
// If click happens inside of a shape then don't set selected nodes to empty
|
// Clicked inside or outside the shape then deselect all of the points/segments
|
||||||
if document.click(input).is_none() {
|
if document.click(input).is_some() && tool_data.stored_selection.is_none() {
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
tool_data.stored_selection = Some(shape_editor.selected_shape_state.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shape_editor.deselect_all_points();
|
||||||
|
shape_editor.deselect_all_segments();
|
||||||
} else {
|
} else {
|
||||||
match selection_shape {
|
match selection_shape {
|
||||||
SelectionShapeType::Box => {
|
SelectionShapeType::Box => {
|
||||||
@@ -2072,8 +2127,8 @@ impl Fsm for PathToolFsmState {
|
|||||||
shape_editor.delete_point_and_break_path(document, responses);
|
shape_editor.delete_point_and_break_path(document, responses);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::FlipSmoothSharp) => {
|
(_, PathToolMessage::DoubleClick { extend_selection, shrink_selection }) => {
|
||||||
// Double-clicked on a point
|
// Double-clicked on a point (flip smooth/sharp behavior)
|
||||||
let nearest_point = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD);
|
let nearest_point = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD);
|
||||||
if nearest_point.is_some() {
|
if nearest_point.is_some() {
|
||||||
// Flip the selected point between smooth and sharp
|
// Flip the selected point between smooth and sharp
|
||||||
@@ -2090,13 +2145,70 @@ impl Fsm for PathToolFsmState {
|
|||||||
|
|
||||||
return PathToolFsmState::Ready;
|
return PathToolFsmState::Ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double-clicked on a filled region
|
// Double-clicked on a filled region
|
||||||
if let Some(layer) = document.click(input) {
|
else if let Some(layer) = document.click(input) {
|
||||||
// Select all points in the layer
|
let extend_selection = input.keyboard.get(extend_selection as usize);
|
||||||
shape_editor.select_connected_anchors(document, layer, input.mouse.position);
|
let shrink_selection = input.keyboard.get(shrink_selection as usize);
|
||||||
|
|
||||||
|
if shape_editor.is_selected_layer(layer) {
|
||||||
|
if extend_selection && !tool_data.first_selected_with_single_click {
|
||||||
|
responses.add(NodeGraphMessage::SelectedNodesRemove { nodes: vec![layer.to_node()] });
|
||||||
|
|
||||||
|
if let Some(selection) = &tool_data.stored_selection {
|
||||||
|
let mut selection = selection.clone();
|
||||||
|
selection.remove(&layer);
|
||||||
|
shape_editor.selected_shape_state = selection;
|
||||||
|
tool_data.stored_selection = None;
|
||||||
|
}
|
||||||
|
} else if shrink_selection && !tool_data.first_selected_with_single_click {
|
||||||
|
// Only deselect all the points of the double clicked layer
|
||||||
|
if let Some(selection) = &tool_data.stored_selection {
|
||||||
|
let selection = selection.clone();
|
||||||
|
shape_editor.selected_shape_state = selection;
|
||||||
|
tool_data.stored_selection = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = shape_editor.selected_shape_state.get_mut(&layer).expect("No state for selected layer");
|
||||||
|
state.deselect_all_points_in_layer();
|
||||||
|
state.deselect_all_segments_in_layer();
|
||||||
|
} else if !tool_data.first_selected_with_single_click {
|
||||||
|
// Select according to the selected editing mode
|
||||||
|
let point_editing_mode = tool_options.path_editing_mode.point_editing_mode;
|
||||||
|
let segment_editing_mode = tool_options.path_editing_mode.segment_editing_mode;
|
||||||
|
shape_editor.select_connected(document, layer, input.mouse.position, point_editing_mode, segment_editing_mode);
|
||||||
|
|
||||||
|
// Select all the other layers back again
|
||||||
|
if let Some(selection) = &tool_data.stored_selection {
|
||||||
|
let mut selection = selection.clone();
|
||||||
|
selection.remove(&layer);
|
||||||
|
|
||||||
|
for (layer, state) in selection {
|
||||||
|
shape_editor.selected_shape_state.insert(layer, state);
|
||||||
|
}
|
||||||
|
tool_data.stored_selection = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it was the very first click without there being an existing selection,
|
||||||
|
// then the single-click behavior and double-click behavior should not collide
|
||||||
|
tool_data.first_selected_with_single_click = false;
|
||||||
|
} else if extend_selection {
|
||||||
|
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
|
||||||
|
|
||||||
|
if let Some(selection) = &tool_data.stored_selection {
|
||||||
|
shape_editor.selected_shape_state = selection.clone();
|
||||||
|
tool_data.stored_selection = None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
|
||||||
|
}
|
||||||
|
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
}
|
}
|
||||||
|
// Double clicked on the background
|
||||||
|
else {
|
||||||
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||||
|
}
|
||||||
|
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
@@ -2163,6 +2275,18 @@ impl Fsm for PathToolFsmState {
|
|||||||
responses.add(DocumentMessage::EndTransaction);
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
|
(_, PathToolMessage::SetPivot { position }) => {
|
||||||
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
|
tool_data.pivot_gizmo.pivot.last_non_none_reference_point = position;
|
||||||
|
let position: Option<DVec2> = position.into();
|
||||||
|
tool_data.pivot_gizmo.pivot.set_normalized_position(position.unwrap());
|
||||||
|
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||||
|
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
(_, _) => PathToolFsmState::Ready,
|
(_, _) => PathToolFsmState::Ready,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2235,7 +2359,10 @@ fn get_selection_status(network_interface: &NodeNetworkInterface, shape_state: &
|
|||||||
return SelectionStatus::None;
|
return SelectionStatus::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let coordinates = network_interface.document_metadata().transform_to_document(layer).transform_point2(local_position);
|
let coordinates = network_interface
|
||||||
|
.document_metadata()
|
||||||
|
.transform_to_document_if_feeds(layer, network_interface)
|
||||||
|
.transform_point2(local_position);
|
||||||
let manipulator_angle = if vector_data.colinear(point) { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free };
|
let manipulator_angle = if vector_data.colinear(point) { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free };
|
||||||
|
|
||||||
return SelectionStatus::One(SingleSelectedPoint {
|
return SelectionStatus::One(SingleSelectedPoint {
|
||||||
@@ -2529,7 +2656,40 @@ fn update_dynamic_hints(
|
|||||||
dragging_hint_data.0.push(HintGroup(hold_group));
|
dragging_hint_data.0.push(HintGroup(hold_group));
|
||||||
}
|
}
|
||||||
|
|
||||||
dragging_hint_data
|
if tool_data.molding_segment {
|
||||||
|
let mut has_colinear_anchors = false;
|
||||||
|
|
||||||
|
if let Some(segment) = &tool_data.segment {
|
||||||
|
let handle1 = HandleId::primary(segment.segment());
|
||||||
|
let handle2 = HandleId::end(segment.segment());
|
||||||
|
|
||||||
|
if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) {
|
||||||
|
let other_handle1 = vector_data.other_colinear_handle(handle1);
|
||||||
|
let other_handle2 = vector_data.other_colinear_handle(handle2);
|
||||||
|
if other_handle1.is_some() || other_handle2.is_some() {
|
||||||
|
has_colinear_anchors = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let handles_stored = if let Some(other_handles) = tool_data.temporary_adjacent_handles_while_molding {
|
||||||
|
other_handles[0].is_some() || other_handles[1].is_some()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let molding_disable_possible = has_colinear_anchors || handles_stored;
|
||||||
|
|
||||||
|
let mut molding_hints = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])];
|
||||||
|
|
||||||
|
if molding_disable_possible {
|
||||||
|
molding_hints.push(HintGroup(vec![HintInfo::keys([Key::Alt], "Break Colinear Handles")]));
|
||||||
|
}
|
||||||
|
|
||||||
|
HintData(molding_hints)
|
||||||
|
} else {
|
||||||
|
dragging_hint_data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
PathToolFsmState::Drawing { .. } => HintData(vec![
|
PathToolFsmState::Drawing { .. } => HintData(vec![
|
||||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||||
@@ -2539,38 +2699,6 @@ fn update_dynamic_hints(
|
|||||||
HintInfo::keys([Key::Alt], "Subtract").prepend_plus(),
|
HintInfo::keys([Key::Alt], "Subtract").prepend_plus(),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
PathToolFsmState::MoldingSegment => {
|
|
||||||
let mut has_colinear_anchors = false;
|
|
||||||
|
|
||||||
if let Some(segment) = &tool_data.segment {
|
|
||||||
let handle1 = HandleId::primary(segment.segment());
|
|
||||||
let handle2 = HandleId::end(segment.segment());
|
|
||||||
|
|
||||||
if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) {
|
|
||||||
let other_handle1 = vector_data.other_colinear_handle(handle1);
|
|
||||||
let other_handle2 = vector_data.other_colinear_handle(handle2);
|
|
||||||
if other_handle1.is_some() || other_handle2.is_some() {
|
|
||||||
has_colinear_anchors = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let handles_stored = if let Some(other_handles) = tool_data.temporary_adjacent_handles_while_molding {
|
|
||||||
other_handles[0].is_some() || other_handles[1].is_some()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
let molding_disable_possible = has_colinear_anchors || handles_stored;
|
|
||||||
|
|
||||||
let mut molding_hints = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])];
|
|
||||||
|
|
||||||
if molding_disable_possible {
|
|
||||||
molding_hints.push(HintGroup(vec![HintInfo::keys([Key::Alt], "Break Colinear Handles")]));
|
|
||||||
}
|
|
||||||
|
|
||||||
HintData(molding_hints)
|
|
||||||
}
|
|
||||||
PathToolFsmState::SlidingPoint => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
|
PathToolFsmState::SlidingPoint => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
|
||||||
};
|
};
|
||||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use graphene_std::Color;
|
|||||||
use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, StrokeId, VectorData};
|
use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, StrokeId, VectorData};
|
||||||
use graphene_std::vector::{PointId, VectorModificationType};
|
use graphene_std::vector::{PointId, VectorModificationType};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct PenTool {
|
pub struct PenTool {
|
||||||
fsm_state: PenToolFsmState,
|
fsm_state: PenToolFsmState,
|
||||||
tool_data: PenToolData,
|
tool_data: PenToolData,
|
||||||
@@ -186,6 +186,7 @@ impl LayoutHolder for PenTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool {
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool {
|
||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||||
let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else {
|
let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else {
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
|||||||
use crate::messages::preferences::SelectionMode;
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose};
|
use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose};
|
||||||
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
|
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
|
||||||
use crate::messages::tool::common_functionality::measure;
|
use crate::messages::tool::common_functionality::measure;
|
||||||
use crate::messages::tool::common_functionality::pivot::Pivot;
|
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget};
|
||||||
use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType;
|
use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType;
|
||||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
|
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
|
||||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||||
@@ -28,7 +29,7 @@ use graphene_std::renderer::Rect;
|
|||||||
use graphene_std::transform::ReferencePoint;
|
use graphene_std::transform::ReferencePoint;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct SelectTool {
|
pub struct SelectTool {
|
||||||
fsm_state: SelectToolFsmState,
|
fsm_state: SelectToolFsmState,
|
||||||
tool_data: SelectToolData,
|
tool_data: SelectToolData,
|
||||||
@@ -43,6 +44,9 @@ pub struct SelectOptions {
|
|||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
pub enum SelectOptionsUpdate {
|
pub enum SelectOptionsUpdate {
|
||||||
NestedSelectionBehavior(NestedSelectionBehavior),
|
NestedSelectionBehavior(NestedSelectionBehavior),
|
||||||
|
PivotGizmoType(PivotGizmoType),
|
||||||
|
TogglePivotGizmoType(bool),
|
||||||
|
TogglePivotPinned,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
@@ -95,6 +99,14 @@ pub enum SelectToolMessage {
|
|||||||
SetPivot {
|
SetPivot {
|
||||||
position: ReferencePoint,
|
position: ReferencePoint,
|
||||||
},
|
},
|
||||||
|
SyncHistory,
|
||||||
|
ShiftSelectedNodes {
|
||||||
|
offset: DVec2,
|
||||||
|
},
|
||||||
|
PivotShift {
|
||||||
|
offset: Option<DVec2>,
|
||||||
|
flush: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToolMetadata for SelectTool {
|
impl ToolMetadata for SelectTool {
|
||||||
@@ -122,14 +134,12 @@ impl SelectTool {
|
|||||||
|
|
||||||
DropdownInput::new(vec![layer_selection_behavior_entries])
|
DropdownInput::new(vec![layer_selection_behavior_entries])
|
||||||
.selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest) as u32))
|
.selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest) as u32))
|
||||||
.tooltip("Choose if clicking nested layers directly selects the deepest, or selects the shallowest and deepens by double clicking")
|
.tooltip(
|
||||||
.widget_holder()
|
"Selection Mode\n\
|
||||||
}
|
\n\
|
||||||
|
Shallow Select: clicks initially select the least-nested layers and double clicks drill deeper into the folder hierarchy.\n\
|
||||||
fn pivot_reference_point_widget(&self, disabled: bool) -> WidgetHolder {
|
Deep Select: clicks directly select the most-nested layers in the folder hierarchy.",
|
||||||
ReferencePointInput::new(self.tool_data.pivot.to_pivot_position())
|
)
|
||||||
.on_update(|pivot_input: &ReferencePointInput| SelectToolMessage::SetPivot { position: pivot_input.value }.into())
|
|
||||||
.disabled(disabled)
|
|
||||||
.widget_holder()
|
.widget_holder()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +188,7 @@ impl SelectTool {
|
|||||||
|
|
||||||
fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> + use<> {
|
fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> + use<> {
|
||||||
let list = <BooleanOperation as graphene_std::registry::ChoiceTypeStatic>::list();
|
let list = <BooleanOperation as graphene_std::registry::ChoiceTypeStatic>::list();
|
||||||
list.into_iter().map(|i| i.into_iter()).flatten().map(move |(operation, info)| {
|
list.iter().flat_map(|i| i.iter()).map(move |(operation, info)| {
|
||||||
let mut tooltip = info.label.to_string();
|
let mut tooltip = info.label.to_string();
|
||||||
if let Some(doc) = info.docstring.as_deref() {
|
if let Some(doc) = info.docstring.as_deref() {
|
||||||
tooltip.push_str("\n\n");
|
tooltip.push_str("\n\n");
|
||||||
@@ -203,9 +213,29 @@ impl LayoutHolder for SelectTool {
|
|||||||
// Select mode (Deep/Shallow)
|
// Select mode (Deep/Shallow)
|
||||||
widgets.push(self.deep_selection_widget());
|
widgets.push(self.deep_selection_widget());
|
||||||
|
|
||||||
// Pivot
|
// Pivot gizmo type (checkbox + dropdown for pivot/origin)
|
||||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
widgets.push(self.pivot_reference_point_widget(self.tool_data.selected_layers_count == 0));
|
widgets.extend(pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Select));
|
||||||
|
|
||||||
|
if self.tool_data.pivot_gizmo.state.is_pivot_type() {
|
||||||
|
// Nine-position reference point widget
|
||||||
|
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
|
||||||
|
widgets.push(pivot_reference_point_widget(
|
||||||
|
self.tool_data.selected_layers_count == 0 || !self.tool_data.pivot_gizmo.state.is_pivot(),
|
||||||
|
self.tool_data.pivot_gizmo.pivot.to_pivot_position(),
|
||||||
|
PivotToolSource::Select,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Pivot pin button
|
||||||
|
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
|
||||||
|
|
||||||
|
let pin_active = self.tool_data.pivot_gizmo.pin_active();
|
||||||
|
let pin_enabled = self.tool_data.pivot_gizmo.pivot.old_pivot_position == ReferencePoint::None && !self.tool_data.pivot_gizmo.state.disabled;
|
||||||
|
|
||||||
|
if pin_active || pin_enabled {
|
||||||
|
widgets.push(pin_pivot_widget(pin_active, pin_enabled, PivotToolSource::Select));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Align
|
// Align
|
||||||
let disabled = self.tool_data.selected_layers_count < 2;
|
let disabled = self.tool_data.selected_layers_count < 2;
|
||||||
@@ -242,16 +272,46 @@ impl LayoutHolder for SelectTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SelectTool {
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SelectTool {
|
||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||||
if let ToolMessage::Select(SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior))) = message {
|
let mut redraw_reference_pivot = false;
|
||||||
self.tool_data.nested_selection_behavior = nested_selection_behavior;
|
|
||||||
responses.add(ToolMessage::UpdateHints);
|
if let ToolMessage::Select(SelectToolMessage::SelectOptions(ref option_update)) = message {
|
||||||
|
match option_update {
|
||||||
|
SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior) => {
|
||||||
|
self.tool_data.nested_selection_behavior = *nested_selection_behavior;
|
||||||
|
responses.add(ToolMessage::UpdateHints);
|
||||||
|
}
|
||||||
|
SelectOptionsUpdate::PivotGizmoType(gizmo_type) => {
|
||||||
|
if !self.tool_data.pivot_gizmo.state.disabled {
|
||||||
|
self.tool_data.pivot_gizmo.state.gizmo_type = *gizmo_type;
|
||||||
|
responses.add(ToolMessage::UpdateHints);
|
||||||
|
let pivot_gizmo = self.tool_data.pivot_gizmo();
|
||||||
|
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
redraw_reference_pivot = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SelectOptionsUpdate::TogglePivotGizmoType(state) => {
|
||||||
|
self.tool_data.pivot_gizmo.state.disabled = !state;
|
||||||
|
responses.add(ToolMessage::UpdateHints);
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
redraw_reference_pivot = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectOptionsUpdate::TogglePivotPinned => {
|
||||||
|
self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;
|
||||||
|
responses.add(ToolMessage::UpdateHints);
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
redraw_reference_pivot = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, false);
|
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, false);
|
||||||
|
|
||||||
if self.tool_data.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed {
|
if self.tool_data.pivot_gizmo.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed || redraw_reference_pivot {
|
||||||
// Send the layout containing the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool)
|
// Send the layout containing the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool)
|
||||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||||
self.tool_data.selected_layers_changed = false;
|
self.tool_data.selected_layers_changed = false;
|
||||||
@@ -323,7 +383,8 @@ struct SelectToolData {
|
|||||||
drag_current: ViewportPosition,
|
drag_current: ViewportPosition,
|
||||||
lasso_polygon: Vec<ViewportPosition>,
|
lasso_polygon: Vec<ViewportPosition>,
|
||||||
selection_mode: Option<SelectionMode>,
|
selection_mode: Option<SelectionMode>,
|
||||||
layers_dragging: Vec<LayerNodeIdentifier>,
|
layers_dragging: Vec<LayerNodeIdentifier>, // Unordered, often used as temporary buffer
|
||||||
|
ordered_layers: Vec<LayerNodeIdentifier>, // Ordered list of layers
|
||||||
layer_selected_on_start: Option<LayerNodeIdentifier>,
|
layer_selected_on_start: Option<LayerNodeIdentifier>,
|
||||||
select_single_layer: Option<LayerNodeIdentifier>,
|
select_single_layer: Option<LayerNodeIdentifier>,
|
||||||
axis_align: bool,
|
axis_align: bool,
|
||||||
@@ -331,7 +392,9 @@ struct SelectToolData {
|
|||||||
bounding_box_manager: Option<BoundingBoxManager>,
|
bounding_box_manager: Option<BoundingBoxManager>,
|
||||||
snap_manager: SnapManager,
|
snap_manager: SnapManager,
|
||||||
cursor: MouseCursorIcon,
|
cursor: MouseCursorIcon,
|
||||||
pivot: Pivot,
|
pivot_gizmo: PivotGizmo,
|
||||||
|
pivot_gizmo_start: Option<DVec2>,
|
||||||
|
pivot_gizmo_shift: Option<DVec2>,
|
||||||
compass_rose: CompassRose,
|
compass_rose: CompassRose,
|
||||||
line_center: DVec2,
|
line_center: DVec2,
|
||||||
skew_edge: EdgeBool,
|
skew_edge: EdgeBool,
|
||||||
@@ -497,6 +560,24 @@ impl SelectToolData {
|
|||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
self.layers_dragging = original;
|
self.layers_dragging = original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn state_from_pivot_gizmo(&self, mouse: DVec2) -> Option<SelectToolFsmState> {
|
||||||
|
match self.pivot_gizmo.state.gizmo_type {
|
||||||
|
PivotGizmoType::Pivot if self.pivot_gizmo.state.is_pivot() => self.pivot_gizmo.pivot.is_over(mouse).then_some(SelectToolFsmState::DraggingPivot),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pivot_gizmo(&self) -> PivotGizmo {
|
||||||
|
self.pivot_gizmo.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_history(&mut self, document: &DocumentMessageHandler) {
|
||||||
|
let layers: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect();
|
||||||
|
self.ordered_layers.retain(|layer| layers.contains(layer));
|
||||||
|
self.ordered_layers.extend(layers.iter().find(|&layer| !self.ordered_layers.contains(layer)));
|
||||||
|
self.pivot_gizmo.layer = self.ordered_layers.last().copied()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fsm for SelectToolFsmState {
|
impl Fsm for SelectToolFsmState {
|
||||||
@@ -710,8 +791,63 @@ impl Fsm for SelectToolFsmState {
|
|||||||
.flatten()
|
.flatten()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update pivot
|
let mut active_origin = None;
|
||||||
tool_data.pivot.update_pivot(document, &mut overlay_context, Some((angle,)));
|
let mut origin_angle = 0.;
|
||||||
|
if overlay_context.visibility_settings.origin() && !tool_data.pivot_gizmo.state.is_pivot_type() {
|
||||||
|
let get_angle = |layer: LayerNodeIdentifier| -> f64 {
|
||||||
|
let quad = Quad::from_box([DVec2::ZERO, DVec2::ONE]);
|
||||||
|
let bounds = document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface) * quad;
|
||||||
|
(bounds.top_left() - bounds.top_right()).to_angle()
|
||||||
|
};
|
||||||
|
if tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Average {
|
||||||
|
let mut count = 0_usize;
|
||||||
|
|
||||||
|
let sum: f64 = document
|
||||||
|
.network_interface
|
||||||
|
.selected_nodes()
|
||||||
|
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||||
|
.map(get_angle)
|
||||||
|
.inspect(|_| count += 1)
|
||||||
|
.sum();
|
||||||
|
if count > 0 {
|
||||||
|
origin_angle = sum / count as f64;
|
||||||
|
}
|
||||||
|
} else if tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Active {
|
||||||
|
origin_angle = document
|
||||||
|
.network_interface
|
||||||
|
.selected_nodes()
|
||||||
|
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||||
|
.find(|&layer| Some(layer) == tool_data.pivot_gizmo.layer)
|
||||||
|
.iter()
|
||||||
|
.map(|&layer| get_angle(layer))
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {
|
||||||
|
let origin = graph_modification_utils::get_viewport_origin(layer, &document.network_interface);
|
||||||
|
if Some(layer) == tool_data.pivot_gizmo.layer {
|
||||||
|
active_origin = Some(origin);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
overlay_context.dowel_pin(origin, origin_angle, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(origin) = active_origin {
|
||||||
|
overlay_context.dowel_pin(origin, origin_angle, Some(COLOR_OVERLAY_YELLOW));
|
||||||
|
}
|
||||||
|
|
||||||
|
let has_layers = document.network_interface.selected_nodes().has_selected_nodes();
|
||||||
|
let draw_pivot = tool_data.pivot_gizmo.state.is_pivot() && overlay_context.visibility_settings.pivot() && has_layers;
|
||||||
|
tool_data.pivot_gizmo.pivot.recalculate_pivot(document);
|
||||||
|
let pivot = draw_pivot.then_some(tool_data.pivot_gizmo.pivot.pivot).flatten();
|
||||||
|
if let Some(pivot) = pivot {
|
||||||
|
let offset = tool_data
|
||||||
|
.pivot_gizmo_start
|
||||||
|
.map(|offset| tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - offset).unwrap_or_default())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let shift = tool_data.pivot_gizmo_shift.unwrap_or_default();
|
||||||
|
overlay_context.pivot(pivot + offset + shift, angle);
|
||||||
|
}
|
||||||
|
|
||||||
// Update compass rose
|
// Update compass rose
|
||||||
if overlay_context.visibility_settings.compass_rose() {
|
if overlay_context.visibility_settings.compass_rose() {
|
||||||
@@ -837,6 +973,15 @@ impl Fsm for SelectToolFsmState {
|
|||||||
(SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, None, fill_color),
|
(SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, None, fill_color),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Self::Dragging { .. } = self {
|
||||||
|
let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]);
|
||||||
|
let document_start = document.metadata().document_to_viewport.inverse().transform_point2(quad.top_left());
|
||||||
|
let document_current = document.metadata().document_to_viewport.inverse().transform_point2(quad.bottom_right());
|
||||||
|
|
||||||
|
overlay_context.translation_box(document_current - document_start, quad, None);
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(_, SelectToolMessage::EditLayer) => {
|
(_, SelectToolMessage::EditLayer) => {
|
||||||
@@ -868,7 +1013,8 @@ impl Fsm for SelectToolFsmState {
|
|||||||
let intersection_list = document.click_list(input).collect::<Vec<_>>();
|
let intersection_list = document.click_list(input).collect::<Vec<_>>();
|
||||||
let intersection = document.find_deepest(&intersection_list);
|
let intersection = document.find_deepest(&intersection_list);
|
||||||
|
|
||||||
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging);
|
let position = tool_data.pivot_gizmo().position(document);
|
||||||
|
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, Some(position));
|
||||||
|
|
||||||
// If the user is dragging the bounding box bounds, go into ResizingBounds mode.
|
// If the user is dragging the bounding box bounds, go into ResizingBounds mode.
|
||||||
// If the user is dragging the rotate trigger, go into RotatingBounds mode.
|
// If the user is dragging the rotate trigger, go into RotatingBounds mode.
|
||||||
@@ -883,20 +1029,17 @@ impl Fsm for SelectToolFsmState {
|
|||||||
let angle = bounds.map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle());
|
let angle = bounds.map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle());
|
||||||
let mouse_position = input.mouse.position;
|
let mouse_position = input.mouse.position;
|
||||||
let compass_rose_state = tool_data.compass_rose.compass_rose_state(mouse_position, angle);
|
let compass_rose_state = tool_data.compass_rose.compass_rose_state(mouse_position, angle);
|
||||||
let is_over_pivot = tool_data.pivot.is_over(mouse_position);
|
|
||||||
|
|
||||||
let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position));
|
let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position));
|
||||||
let can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none());
|
let can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none());
|
||||||
|
|
||||||
let state = if is_over_pivot
|
let state = if let Some(state) = tool_data.state_from_pivot_gizmo(input.mouse.position) {
|
||||||
// Dragging the pivot
|
|
||||||
{
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
// tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
// tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||||
// tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
// tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||||
|
|
||||||
SelectToolFsmState::DraggingPivot
|
state
|
||||||
}
|
}
|
||||||
// Dragging one (or two, forming a corner) of the transform cage bounding box edges
|
// Dragging one (or two, forming a corner) of the transform cage bounding box edges
|
||||||
else if resize {
|
else if resize {
|
||||||
@@ -917,12 +1060,14 @@ impl Fsm for SelectToolFsmState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tool_data.layers_dragging = selected;
|
tool_data.layers_dragging = selected;
|
||||||
|
|
||||||
tool_data.get_snap_candidates(document, input);
|
tool_data.get_snap_candidates(document, input);
|
||||||
let (axis, using_compass) = {
|
let (axis, using_compass) = {
|
||||||
let axis_state = compass_rose_state.axis_type().filter(|_| can_grab_compass_rose);
|
let axis_state = compass_rose_state.axis_type().filter(|_| can_grab_compass_rose);
|
||||||
(axis_state.unwrap_or_default(), axis_state.is_some())
|
(axis_state.unwrap_or_default(), axis_state.is_some())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
tool_data.pivot_gizmo_start = Some(tool_data.drag_current);
|
||||||
|
|
||||||
SelectToolFsmState::Dragging {
|
SelectToolFsmState::Dragging {
|
||||||
axis,
|
axis,
|
||||||
using_compass,
|
using_compass,
|
||||||
@@ -941,6 +1086,12 @@ impl Fsm for SelectToolFsmState {
|
|||||||
let extend = input.keyboard.key(extend_selection);
|
let extend = input.keyboard.key(extend_selection);
|
||||||
if !extend && !input.keyboard.key(remove_from_selection) {
|
if !extend && !input.keyboard.key(remove_from_selection) {
|
||||||
responses.add(DocumentMessage::DeselectAllLayers);
|
responses.add(DocumentMessage::DeselectAllLayers);
|
||||||
|
|
||||||
|
if !tool_data.pivot_gizmo.pivot.pinned {
|
||||||
|
let position = tool_data.pivot_gizmo.pivot.last_non_none_reference_point;
|
||||||
|
responses.add(SelectToolMessage::SetPivot { position });
|
||||||
|
}
|
||||||
|
|
||||||
tool_data.layers_dragging.clear();
|
tool_data.layers_dragging.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -955,6 +1106,9 @@ impl Fsm for SelectToolFsmState {
|
|||||||
tool_data.get_snap_candidates(document, input);
|
tool_data.get_snap_candidates(document, input);
|
||||||
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
|
tool_data.pivot_gizmo_start = Some(tool_data.drag_current);
|
||||||
|
|
||||||
SelectToolFsmState::Dragging {
|
SelectToolFsmState::Dragging {
|
||||||
axis: Axis::None,
|
axis: Axis::None,
|
||||||
using_compass: false,
|
using_compass: false,
|
||||||
@@ -1098,7 +1252,10 @@ impl Fsm for SelectToolFsmState {
|
|||||||
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => {
|
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => {
|
||||||
let mouse_position = input.mouse.position;
|
let mouse_position = input.mouse.position;
|
||||||
let snapped_mouse_position = mouse_position;
|
let snapped_mouse_position = mouse_position;
|
||||||
tool_data.pivot.set_viewport_position(snapped_mouse_position, document, responses);
|
|
||||||
|
tool_data.pivot_gizmo.pivot.set_viewport_position(snapped_mouse_position);
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
let messages = [
|
let messages = [
|
||||||
@@ -1143,7 +1300,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
.map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge)));
|
.map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge)));
|
||||||
|
|
||||||
// Dragging the pivot overrules the other operations
|
// Dragging the pivot overrules the other operations
|
||||||
if tool_data.pivot.is_over(input.mouse.position) {
|
if tool_data.state_from_pivot_gizmo(input.mouse.position).is_some() {
|
||||||
cursor = MouseCursorIcon::Move;
|
cursor = MouseCursorIcon::Move;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1283,20 +1440,32 @@ impl Fsm for SelectToolFsmState {
|
|||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
tool_data.select_single_layer = None;
|
tool_data.select_single_layer = None;
|
||||||
|
|
||||||
|
if let Some(start) = tool_data.pivot_gizmo_start {
|
||||||
|
let offset = tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - start).unwrap_or_default();
|
||||||
|
if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {
|
||||||
|
*v += offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tool_data.pivot_gizmo_start = None;
|
||||||
|
|
||||||
|
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||||
|
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||||
|
|
||||||
let selection = tool_data.nested_selection_behavior;
|
let selection = tool_data.nested_selection_behavior;
|
||||||
SelectToolFsmState::Ready { selection }
|
SelectToolFsmState::Ready { selection }
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
SelectToolFsmState::ResizingBounds
|
SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot,
|
||||||
| SelectToolFsmState::SkewingBounds { .. }
|
|
||||||
| SelectToolFsmState::RotatingBounds
|
|
||||||
| SelectToolFsmState::Dragging { .. }
|
|
||||||
| SelectToolFsmState::DraggingPivot,
|
|
||||||
SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter,
|
SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter,
|
||||||
) => {
|
) => {
|
||||||
let drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON;
|
let drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON;
|
||||||
let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };
|
let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };
|
||||||
|
|
||||||
|
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||||
|
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||||
|
|
||||||
responses.add(response);
|
responses.add(response);
|
||||||
|
|
||||||
tool_data.axis_align = false;
|
tool_data.axis_align = false;
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
|
|
||||||
@@ -1432,8 +1601,48 @@ impl Fsm for SelectToolFsmState {
|
|||||||
(_, SelectToolMessage::SetPivot { position }) => {
|
(_, SelectToolMessage::SetPivot { position }) => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
|
tool_data.pivot_gizmo.pivot.last_non_none_reference_point = position;
|
||||||
|
tool_data.pivot_gizmo.pivot.pinned = false;
|
||||||
|
|
||||||
let pos: Option<DVec2> = position.into();
|
let pos: Option<DVec2> = position.into();
|
||||||
tool_data.pivot.set_normalized_position(pos.unwrap(), document, responses);
|
|
||||||
|
tool_data.pivot_gizmo.pivot.set_normalized_position(pos.unwrap());
|
||||||
|
|
||||||
|
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||||
|
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
(_, SelectToolMessage::SyncHistory) => {
|
||||||
|
tool_data.sync_history(document);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
(_, SelectToolMessage::ShiftSelectedNodes { offset }) => {
|
||||||
|
let offset = document.metadata().document_to_viewport.transform_vector2(offset);
|
||||||
|
if tool_data.pivot_gizmo.pivot_disconnected() {
|
||||||
|
if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {
|
||||||
|
*v += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||||
|
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
(_, SelectToolMessage::PivotShift { offset, flush }) => {
|
||||||
|
if flush {
|
||||||
|
tool_data.pivot_gizmo.pivot.pivot.as_mut().map(|v| *v += tool_data.pivot_gizmo_shift.take().unwrap_or_default());
|
||||||
|
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||||
|
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
if tool_data.pivot_gizmo.pivot_disconnected() {
|
||||||
|
tool_data.pivot_gizmo_shift = offset;
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -1658,6 +1867,7 @@ fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<La
|
|||||||
.next()
|
.next()
|
||||||
.expect("ROOT_PARENT should have a layer child when clicking"),
|
.expect("ROOT_PARENT should have a layer child when clicking"),
|
||||||
);
|
);
|
||||||
|
|
||||||
if !remove {
|
if !remove {
|
||||||
tool_data.layers_dragging.extend(vec![layer]);
|
tool_data.layers_dragging.extend(vec![layer]);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -578,7 +578,7 @@ impl Fsm for ShapeToolFsmState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging);
|
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, None);
|
||||||
|
|
||||||
if !input.keyboard.key(Key::Control) {
|
if !input.keyboard.key(Key::Control) {
|
||||||
match (resize, rotate, skew) {
|
match (resize, rotate, skew) {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use graph_craft::document::{NodeId, NodeInput};
|
|||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
|
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct SplineTool {
|
pub struct SplineTool {
|
||||||
fsm_state: SplineToolFsmState,
|
fsm_state: SplineToolFsmState,
|
||||||
tool_data: SplineToolData,
|
tool_data: SplineToolData,
|
||||||
@@ -123,6 +123,7 @@ impl LayoutHolder for SplineTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineTool {
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineTool {
|
||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||||
let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else {
|
let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use crate::messages::portfolio::document::utility_types::network_interface::Inpu
|
|||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name};
|
use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name};
|
||||||
use crate::messages::tool::common_functionality::pivot::Pivot;
|
|
||||||
use crate::messages::tool::common_functionality::resize::Resize;
|
use crate::messages::tool::common_functionality::resize::Resize;
|
||||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData};
|
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData};
|
||||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||||
@@ -21,7 +20,7 @@ use graphene_std::renderer::Quad;
|
|||||||
use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font};
|
use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font};
|
||||||
use graphene_std::vector::style::Fill;
|
use graphene_std::vector::style::Fill;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct TextTool {
|
pub struct TextTool {
|
||||||
fsm_state: TextToolFsmState,
|
fsm_state: TextToolFsmState,
|
||||||
tool_data: TextToolData,
|
tool_data: TextToolData,
|
||||||
@@ -171,6 +170,7 @@ impl LayoutHolder for TextTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextTool {
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextTool {
|
||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||||
let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else {
|
let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else {
|
||||||
@@ -283,7 +283,6 @@ struct TextToolData {
|
|||||||
// Since the overlays must be drawn without knowledge of the inputs
|
// Since the overlays must be drawn without knowledge of the inputs
|
||||||
cached_resize_bounds: [DVec2; 2],
|
cached_resize_bounds: [DVec2; 2],
|
||||||
bounding_box_manager: Option<BoundingBoxManager>,
|
bounding_box_manager: Option<BoundingBoxManager>,
|
||||||
pivot: Pivot,
|
|
||||||
snap_candidates: Vec<SnapCandidatePoint>,
|
snap_candidates: Vec<SnapCandidatePoint>,
|
||||||
// TODO: Handle multiple layers in the future
|
// TODO: Handle multiple layers in the future
|
||||||
layer_dragging: Option<ResizingLayer>,
|
layer_dragging: Option<ResizingLayer>,
|
||||||
@@ -526,7 +525,6 @@ impl Fsm for TextToolFsmState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bounding_box_manager.render_overlays(&mut overlay_context, false);
|
bounding_box_manager.render_overlays(&mut overlay_context, false);
|
||||||
tool_data.pivot.update_pivot(document, &mut overlay_context, None);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tool_data.bounding_box_manager.take();
|
tool_data.bounding_box_manager.take();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
|||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::portfolio::document::utility_types::transformation::TransformType;
|
use crate::messages::portfolio::document::utility_types::transformation::TransformType;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
use crate::messages::tool::common_functionality::pivot::PivotGizmo;
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, TransformLayer)]
|
#[impl_message(Message, ToolMessage, TransformLayer)]
|
||||||
@@ -29,4 +30,5 @@ pub enum TransformLayerMessage {
|
|||||||
TypeDecimalPoint,
|
TypeDecimalPoint,
|
||||||
TypeDigit { digit: u8 },
|
TypeDigit { digit: u8 },
|
||||||
TypeNegate,
|
TypeNegate,
|
||||||
|
SetPivotGizmo { pivot_gizmo: PivotGizmo },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
|||||||
use crate::messages::portfolio::document::utility_types::misc::PTZ;
|
use crate::messages::portfolio::document::utility_types::misc::PTZ;
|
||||||
use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, TransformType, Typing};
|
use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, TransformType, Typing};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType};
|
||||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||||
use crate::messages::tool::utility_types::{ToolData, ToolType};
|
use crate::messages::tool::utility_types::{ToolData, ToolType};
|
||||||
@@ -20,7 +21,7 @@ const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayer
|
|||||||
const SLOW_KEY: Key = Key::Shift;
|
const SLOW_KEY: Key = Key::Shift;
|
||||||
const INCREMENTS_KEY: Key = Key::Control;
|
const INCREMENTS_KEY: Key = Key::Control;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
pub struct TransformLayerMessageHandler {
|
pub struct TransformLayerMessageHandler {
|
||||||
pub transform_operation: TransformOperation,
|
pub transform_operation: TransformOperation,
|
||||||
|
|
||||||
@@ -34,8 +35,11 @@ pub struct TransformLayerMessageHandler {
|
|||||||
start_mouse: ViewportPosition,
|
start_mouse: ViewportPosition,
|
||||||
|
|
||||||
original_transforms: OriginalTransforms,
|
original_transforms: OriginalTransforms,
|
||||||
|
pivot_gizmo: PivotGizmo,
|
||||||
pivot: ViewportPosition,
|
pivot: ViewportPosition,
|
||||||
|
|
||||||
|
path_bounds: Option<[DVec2; 2]>,
|
||||||
|
|
||||||
local_pivot: DocumentPosition,
|
local_pivot: DocumentPosition,
|
||||||
local_mouse_start: DocumentPosition,
|
local_mouse_start: DocumentPosition,
|
||||||
grab_target: DocumentPosition,
|
grab_target: DocumentPosition,
|
||||||
@@ -61,27 +65,64 @@ impl TransformLayerMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_pivot(selected_points: &Vec<&ManipulatorPointId>, vector_data: &VectorData, viewspace: DAffine2, get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>) -> Option<(DVec2, DVec2)> {
|
fn calculate_pivot(
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
selected_points: &Vec<&ManipulatorPointId>,
|
||||||
|
vector_data: &VectorData,
|
||||||
|
viewspace: DAffine2,
|
||||||
|
get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>,
|
||||||
|
gizmo: &mut PivotGizmo,
|
||||||
|
) -> (Option<(DVec2, DVec2)>, Option<[DVec2; 2]>) {
|
||||||
|
let average_position = || {
|
||||||
|
let mut point_count = 0_usize;
|
||||||
|
selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64
|
||||||
|
};
|
||||||
|
let bounds = selected_points.iter().filter_map(|p| get_location(p)).fold(None, |acc: Option<[DVec2; 2]>, point| {
|
||||||
|
if let Some([mut min, mut max]) = acc {
|
||||||
|
min.x = min.x.min(point.x);
|
||||||
|
min.y = min.y.min(point.y);
|
||||||
|
max.x = max.x.max(point.x);
|
||||||
|
max.y = max.y.max(point.y);
|
||||||
|
Some([min, max])
|
||||||
|
} else {
|
||||||
|
Some([point, point])
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gizmo.pivot.recalculate_pivot_for_layer(document, bounds);
|
||||||
|
let position = || {
|
||||||
|
(if !gizmo.state.disabled {
|
||||||
|
match gizmo.state.gizmo_type {
|
||||||
|
PivotGizmoType::Average => None,
|
||||||
|
PivotGizmoType::Active => gizmo.point.and_then(|p| get_location(&p)),
|
||||||
|
PivotGizmoType::Pivot => gizmo.pivot.pivot,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.unwrap_or_else(average_position)
|
||||||
|
};
|
||||||
let [point] = selected_points.as_slice() else {
|
let [point] = selected_points.as_slice() else {
|
||||||
// Handle the case where there are multiple points
|
// Handle the case where there are multiple points
|
||||||
let mut point_count = 0;
|
let position = position();
|
||||||
let average_position = selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64;
|
return (Some((position, position)), bounds);
|
||||||
|
|
||||||
return Some((average_position, average_position));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match point {
|
match point {
|
||||||
ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => {
|
ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => {
|
||||||
// Get the anchor position and transform it to the pivot
|
// Get the anchor position and transform it to the pivot
|
||||||
let pivot_pos = point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position))?;
|
let (Some(pivot_position), Some(position)) = (
|
||||||
let target = viewspace.transform_point2(point.get_position(vector_data)?);
|
point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position)),
|
||||||
Some((pivot_pos, target))
|
point.get_position(vector_data),
|
||||||
|
) else {
|
||||||
|
return (None, None);
|
||||||
|
};
|
||||||
|
let target = viewspace.transform_point2(position);
|
||||||
|
(Some((pivot_position, target)), None)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Calculate the average position of all selected points
|
// Calculate the average position of all selected points
|
||||||
let mut point_count = 0;
|
let position = position();
|
||||||
let average_position = selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64;
|
(Some((position, position)), bounds)
|
||||||
Some((average_position, average_position))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,6 +175,26 @@ fn update_colinear_handles(selected_layers: &[LayerNodeIdentifier], document: &D
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a ToolData, &'a mut ShapeState);
|
type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a ToolData, &'a mut ShapeState);
|
||||||
|
|
||||||
|
pub fn custom_data() -> MessageData {
|
||||||
|
MessageData::new(
|
||||||
|
String::from("TransformData<'a>"),
|
||||||
|
// TODO: When <https://github.com/dtolnay/proc-macro2/issues/503> is resolved and released,
|
||||||
|
// TODO: use <https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.line> to get
|
||||||
|
// TODO: the line number instead of hardcoding it to the magic number on the following lines
|
||||||
|
// TODO: which points to the line of the `type TransformData<'a> = ...` definition above.
|
||||||
|
// TODO: Also, utilize the line number in the actual output, since it is currently unused.
|
||||||
|
vec![
|
||||||
|
(String::from("&'a DocumentMessageHandler"), 177),
|
||||||
|
(String::from("&'a InputPreprocessorMessageHandler"), 177),
|
||||||
|
(String::from("&'a ToolData"), 177),
|
||||||
|
(String::from("&'a mut ShapeState"), 177),
|
||||||
|
],
|
||||||
|
file!(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[message_handler_data(CustomData)]
|
||||||
impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayerMessageHandler {
|
impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayerMessageHandler {
|
||||||
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, (document, input, tool_data, shape_editor): TransformData) {
|
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, (document, input, tool_data, shape_editor): TransformData) {
|
||||||
let using_path_tool = tool_data.active_tool_type == ToolType::Path;
|
let using_path_tool = tool_data.active_tool_type == ToolType::Path;
|
||||||
@@ -177,18 +238,17 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !using_path_tool || !using_shape_tool {
|
if !using_path_tool || !using_shape_tool {
|
||||||
*selected.pivot = selected.mean_average_of_pivots();
|
self.pivot_gizmo.recalculate_transform(document);
|
||||||
|
*selected.pivot = self.pivot_gizmo.position(document);
|
||||||
self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot);
|
self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot);
|
||||||
self.grab_target = document.metadata().document_to_viewport.inverse().transform_point2(selected.mean_average_of_pivots());
|
self.grab_target = self.local_pivot;
|
||||||
}
|
}
|
||||||
// Here vector data from all layers is not considered which can be a problem in pivot calculation
|
// Here vector data from all layers is not considered which can be a problem in pivot calculation
|
||||||
else if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
|
else if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
|
||||||
*selected.original_transforms = OriginalTransforms::default();
|
*selected.original_transforms = OriginalTransforms::default();
|
||||||
|
|
||||||
let viewspace = document.metadata().transform_to_viewport(selected_layers[0]);
|
let viewspace = document.metadata().transform_to_viewport(selected_layers[0]);
|
||||||
|
|
||||||
let selected_segments = shape_editor.selected_segments().collect::<HashSet<_>>();
|
let selected_segments = shape_editor.selected_segments().collect::<HashSet<_>>();
|
||||||
|
|
||||||
let mut affected_points = shape_editor.selected_points().copied().collect::<Vec<_>>();
|
let mut affected_points = shape_editor.selected_points().copied().collect::<Vec<_>>();
|
||||||
|
|
||||||
for (segment_id, _, start, end) in vector_data.segment_bezier_iter() {
|
for (segment_id, _, start, end) in vector_data.segment_bezier_iter() {
|
||||||
@@ -201,8 +261,16 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||||||
let affected_point_refs = affected_points.iter().collect();
|
let affected_point_refs = affected_points.iter().collect();
|
||||||
|
|
||||||
let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position));
|
let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position));
|
||||||
if let Some((new_pivot, grab_target)) = calculate_pivot(&affected_point_refs, &vector_data, viewspace, |point: &ManipulatorPointId| get_location(&point)) {
|
if let (Some((new_pivot, grab_target)), bounds) = calculate_pivot(
|
||||||
|
document,
|
||||||
|
&affected_point_refs,
|
||||||
|
&vector_data,
|
||||||
|
viewspace,
|
||||||
|
|point: &ManipulatorPointId| get_location(&point),
|
||||||
|
&mut self.pivot_gizmo,
|
||||||
|
) {
|
||||||
*selected.pivot = new_pivot;
|
*selected.pivot = new_pivot;
|
||||||
|
self.path_bounds = bounds;
|
||||||
|
|
||||||
self.local_pivot = document_to_viewport.inverse().transform_point2(*selected.pivot);
|
self.local_pivot = document_to_viewport.inverse().transform_point2(*selected.pivot);
|
||||||
self.grab_target = document_to_viewport.inverse().transform_point2(grab_target);
|
self.grab_target = document_to_viewport.inverse().transform_point2(grab_target);
|
||||||
@@ -228,116 +296,93 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for layer in document.metadata().all_layers() {
|
let viewport_box = input.viewport_bounds.size();
|
||||||
if !document.network_interface.is_artboard(&layer.to_node(), &[]) {
|
let axis_constraint = self.transform_operation.axis_constraint();
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let viewport_box = input.viewport_bounds.size();
|
let format_rounded = |value: f64, precision: usize| {
|
||||||
let axis_constraint = self.transform_operation.axis_constraint();
|
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||||
|
format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string()
|
||||||
|
} else {
|
||||||
|
self.typing.string.clone()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let format_rounded = |value: f64, precision: usize| {
|
// TODO: Ensure removing this and adding this doesn't change the position of layers under PTZ ops
|
||||||
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
// responses.add(TransformLayerMessage::PointerMove {
|
||||||
format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string()
|
// slow_key: SLOW_KEY,
|
||||||
|
// increments_key: INCREMENTS_KEY,
|
||||||
|
// });
|
||||||
|
|
||||||
|
match self.transform_operation {
|
||||||
|
TransformOperation::None => (),
|
||||||
|
TransformOperation::Grabbing(translation) => {
|
||||||
|
let translation = translation.to_dvec(self.initial_transform, self.increments);
|
||||||
|
let viewport_translate = document_to_viewport.transform_vector2(translation);
|
||||||
|
let pivot = document_to_viewport.transform_point2(self.grab_target);
|
||||||
|
let quad = Quad::from_box([pivot, pivot + viewport_translate]);
|
||||||
|
|
||||||
|
responses.add(SelectToolMessage::PivotShift {
|
||||||
|
offset: Some(viewport_translate),
|
||||||
|
flush: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone());
|
||||||
|
overlay_context.translation_box(translation, quad, typed_string);
|
||||||
|
}
|
||||||
|
TransformOperation::Scaling(scale) => {
|
||||||
|
let scale = scale.to_f64(self.increments);
|
||||||
|
let text = format!("{}x", format_rounded(scale, 3));
|
||||||
|
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
||||||
|
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
||||||
|
let local_edge = start_mouse - pivot;
|
||||||
|
let local_edge = project_edge_to_quad(local_edge, &self.layer_bounding_box, self.local, axis_constraint);
|
||||||
|
let boundary_point = pivot + local_edge * scale.min(1.);
|
||||||
|
let end_point = pivot + local_edge * scale.max(1.);
|
||||||
|
|
||||||
|
if scale > 0. {
|
||||||
|
overlay_context.dashed_line(pivot, boundary_point, None, None, Some(2.), Some(2.), Some(0.5));
|
||||||
|
}
|
||||||
|
overlay_context.line(boundary_point, end_point, None, None);
|
||||||
|
|
||||||
|
let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.);
|
||||||
|
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||||
|
}
|
||||||
|
TransformOperation::Rotating(rotation) => {
|
||||||
|
let angle = rotation.to_f64(self.increments);
|
||||||
|
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
||||||
|
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
||||||
|
let offset_angle = if self.grs_pen_handle {
|
||||||
|
self.handle - self.last_point
|
||||||
|
} else if using_path_tool {
|
||||||
|
start_mouse - pivot
|
||||||
} else {
|
} else {
|
||||||
self.typing.string.clone()
|
self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right()
|
||||||
}
|
};
|
||||||
};
|
let tilt_offset = document.document_ptz.unmodified_tilt();
|
||||||
|
let offset_angle = offset_angle.to_angle() + tilt_offset;
|
||||||
// TODO: Ensure removing this and adding this doesn't change the position of layers under PTZ ops
|
let width = viewport_box.max_element();
|
||||||
// responses.add(TransformLayerMessage::PointerMove {
|
let radius = start_mouse.distance(pivot);
|
||||||
// slow_key: SLOW_KEY,
|
let arc_radius = ANGLE_MEASURE_RADIUS_FACTOR * width;
|
||||||
// increments_key: INCREMENTS_KEY,
|
let radius = radius.clamp(ARC_MEASURE_RADIUS_FACTOR_RANGE.0 * width, ARC_MEASURE_RADIUS_FACTOR_RANGE.1 * width);
|
||||||
// });
|
let angle_in_degrees = angle.to_degrees();
|
||||||
|
let display_angle = if angle_in_degrees.is_sign_positive() {
|
||||||
match self.transform_operation {
|
angle_in_degrees - (angle_in_degrees / 360.).floor() * 360.
|
||||||
TransformOperation::None => (),
|
} else if angle_in_degrees.is_sign_negative() {
|
||||||
TransformOperation::Grabbing(translation) => {
|
angle_in_degrees - ((angle_in_degrees / 360.).floor() + 1.) * 360.
|
||||||
let translation = translation.to_dvec(self.initial_transform, self.increments);
|
} else {
|
||||||
let viewport_translate = document_to_viewport.transform_vector2(translation);
|
angle_in_degrees
|
||||||
let pivot = document_to_viewport.transform_point2(self.grab_target);
|
};
|
||||||
let quad = Quad::from_box([pivot, pivot + viewport_translate]).0;
|
let text = format!("{}°", format_rounded(display_angle, 2));
|
||||||
let e1 = (self.layer_bounding_box.0[1] - self.layer_bounding_box.0[0]).normalize_or(DVec2::X);
|
let text_texture_width = overlay_context.get_width(&text) / 2.;
|
||||||
|
let text_texture_height = 12.;
|
||||||
if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. {
|
let text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle);
|
||||||
let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] };
|
let text_texture_position = DVec2::new(
|
||||||
overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
|
(arc_radius + 4. + text_texture_width) * text_angle_on_unit_circle.x,
|
||||||
|
(arc_radius + text_texture_height) * text_angle_on_unit_circle.y,
|
||||||
let x_transform = DAffine2::from_translation((quad[0] + end) / 2.);
|
);
|
||||||
overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);
|
let transform = DAffine2::from_translation(text_texture_position + pivot);
|
||||||
}
|
overlay_context.draw_angle(pivot, radius, arc_radius, offset_angle, angle);
|
||||||
|
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||||
if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. {
|
|
||||||
let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] };
|
|
||||||
overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
|
|
||||||
let x_parameter = viewport_translate.x.clamp(-1., 1.);
|
|
||||||
let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.);
|
|
||||||
let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End };
|
|
||||||
if axis_constraint != Axis::Both || self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
|
||||||
overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. {
|
|
||||||
overlay_context.line(quad[1], quad[2], None, None);
|
|
||||||
overlay_context.line(quad[3], quad[2], None, None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TransformOperation::Scaling(scale) => {
|
|
||||||
let scale = scale.to_f64(self.increments);
|
|
||||||
let text = format!("{}x", format_rounded(scale, 3));
|
|
||||||
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
|
||||||
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
|
||||||
let local_edge = start_mouse - pivot;
|
|
||||||
let local_edge = project_edge_to_quad(local_edge, &self.layer_bounding_box, self.local, axis_constraint);
|
|
||||||
let boundary_point = pivot + local_edge * scale.min(1.);
|
|
||||||
let end_point = pivot + local_edge * scale.max(1.);
|
|
||||||
|
|
||||||
if scale > 0. {
|
|
||||||
overlay_context.dashed_line(pivot, boundary_point, None, None, Some(2.), Some(2.), Some(0.5));
|
|
||||||
}
|
|
||||||
overlay_context.line(boundary_point, end_point, None, None);
|
|
||||||
|
|
||||||
let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.);
|
|
||||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
|
||||||
}
|
|
||||||
TransformOperation::Rotating(rotation) => {
|
|
||||||
let angle = rotation.to_f64(self.increments);
|
|
||||||
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
|
||||||
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
|
||||||
let offset_angle = if self.grs_pen_handle {
|
|
||||||
self.handle - self.last_point
|
|
||||||
} else if using_path_tool {
|
|
||||||
start_mouse - pivot
|
|
||||||
} else {
|
|
||||||
self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right()
|
|
||||||
};
|
|
||||||
let tilt_offset = document.document_ptz.unmodified_tilt();
|
|
||||||
let offset_angle = offset_angle.to_angle() + tilt_offset;
|
|
||||||
let width = viewport_box.max_element();
|
|
||||||
let radius = start_mouse.distance(pivot);
|
|
||||||
let arc_radius = ANGLE_MEASURE_RADIUS_FACTOR * width;
|
|
||||||
let radius = radius.clamp(ARC_MEASURE_RADIUS_FACTOR_RANGE.0 * width, ARC_MEASURE_RADIUS_FACTOR_RANGE.1 * width);
|
|
||||||
let angle_in_degrees = angle.to_degrees();
|
|
||||||
let display_angle = if angle_in_degrees.is_sign_positive() {
|
|
||||||
angle_in_degrees - (angle_in_degrees / 360.).floor() * 360.
|
|
||||||
} else if angle_in_degrees.is_sign_negative() {
|
|
||||||
angle_in_degrees - ((angle_in_degrees / 360.).floor() + 1.) * 360.
|
|
||||||
} else {
|
|
||||||
angle_in_degrees
|
|
||||||
};
|
|
||||||
let text = format!("{}°", format_rounded(display_angle, 2));
|
|
||||||
let text_texture_width = overlay_context.get_width(&text) / 2.;
|
|
||||||
let text_texture_height = 12.;
|
|
||||||
let text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle);
|
|
||||||
let text_texture_position = DVec2::new(
|
|
||||||
(arc_radius + 4. + text_texture_width) * text_angle_on_unit_circle.x,
|
|
||||||
(arc_radius + text_texture_height) * text_angle_on_unit_circle.y,
|
|
||||||
);
|
|
||||||
let transform = DAffine2::from_translation(text_texture_position + pivot);
|
|
||||||
overlay_context.draw_angle(pivot, radius, arc_radius, offset_angle, angle);
|
|
||||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,6 +409,8 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
responses.add(SelectToolMessage::PivotShift { offset: None, flush: true });
|
||||||
|
|
||||||
if final_transform {
|
if final_transform {
|
||||||
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
||||||
}
|
}
|
||||||
@@ -487,6 +534,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||||||
responses.add(ToolMessage::UpdateHints);
|
responses.add(ToolMessage::UpdateHints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
responses.add(SelectToolMessage::PivotShift { offset: None, flush: false });
|
||||||
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
||||||
}
|
}
|
||||||
TransformLayerMessage::ConstrainX => {
|
TransformLayerMessage::ConstrainX => {
|
||||||
@@ -694,6 +742,9 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||||||
self.initial_transform,
|
self.initial_transform,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
TransformLayerMessage::SetPivotGizmo { pivot_gizmo } => {
|
||||||
|
self.pivot_gizmo = pivot_gizmo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use graphene_std::text::FontCache;
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt::{self, Debug};
|
use std::fmt::{self, Debug};
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
pub struct ToolActionHandlerData<'a> {
|
pub struct ToolActionHandlerData<'a> {
|
||||||
pub document: &'a mut DocumentMessageHandler,
|
pub document: &'a mut DocumentMessageHandler,
|
||||||
pub document_id: DocumentId,
|
pub document_id: DocumentId,
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
pub struct WorkspaceMessageHandler {
|
pub struct WorkspaceMessageHandler {
|
||||||
node_graph_visible: bool,
|
node_graph_visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
impl MessageHandler<WorkspaceMessage, ()> for WorkspaceMessageHandler {
|
impl MessageHandler<WorkspaceMessage, ()> for WorkspaceMessageHandler {
|
||||||
fn process_message(&mut self, message: WorkspaceMessage, _responses: &mut VecDeque<Message>, _data: ()) {
|
fn process_message(&mut self, message: WorkspaceMessage, _responses: &mut VecDeque<Message>, _data: ()) {
|
||||||
match message {
|
match message {
|
||||||
|
|||||||
@@ -413,6 +413,7 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||||
use crate::test_utils::test_prelude::{self, NodeGraphLayer};
|
use crate::test_utils::test_prelude::{self, NodeGraphLayer};
|
||||||
|
use graph_craft::ProtoNodeIdentifier;
|
||||||
use graph_craft::document::NodeNetwork;
|
use graph_craft::document::NodeNetwork;
|
||||||
use graphene_std::Context;
|
use graphene_std::Context;
|
||||||
use graphene_std::NodeInputDecleration;
|
use graphene_std::NodeInputDecleration;
|
||||||
@@ -422,7 +423,7 @@ mod test {
|
|||||||
/// Stores all of the monitor nodes that have been attached to a graph
|
/// Stores all of the monitor nodes that have been attached to a graph
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Instrumented {
|
pub struct Instrumented {
|
||||||
protonodes_by_name: HashMap<String, Vec<Vec<Vec<NodeId>>>>,
|
protonodes_by_name: HashMap<ProtoNodeIdentifier, Vec<Vec<Vec<NodeId>>>>,
|
||||||
protonodes_by_path: HashMap<Vec<NodeId>, Vec<Vec<NodeId>>>,
|
protonodes_by_path: HashMap<Vec<NodeId>, Vec<Vec<NodeId>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,7 +450,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation {
|
if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation {
|
||||||
path.push(*id);
|
path.push(*id);
|
||||||
self.protonodes_by_name.entry(identifier.name.to_string()).or_default().push(monitor_node_ids.clone());
|
self.protonodes_by_name.entry(identifier.clone()).or_default().push(monitor_node_ids.clone());
|
||||||
self.protonodes_by_path.insert(path.clone(), monitor_node_ids);
|
self.protonodes_by_path.insert(path.clone(), monitor_node_ids);
|
||||||
path.pop();
|
path.pop();
|
||||||
}
|
}
|
||||||
@@ -457,7 +458,7 @@ mod test {
|
|||||||
for (input, monitor_id) in monitor_nodes {
|
for (input, monitor_id) in monitor_nodes {
|
||||||
let monitor_node = DocumentNode {
|
let monitor_node = DocumentNode {
|
||||||
inputs: vec![input],
|
inputs: vec![input],
|
||||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
|
||||||
manual_composition: Some(graph_craft::generic!(T)),
|
manual_composition: Some(graph_craft::generic!(T)),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -495,7 +496,7 @@ mod test {
|
|||||||
Input::Result: Send + Sync + Clone + 'static,
|
Input::Result: Send + Sync + Clone + 'static,
|
||||||
{
|
{
|
||||||
self.protonodes_by_name
|
self.protonodes_by_name
|
||||||
.get(Input::identifier())
|
.get(&Input::identifier())
|
||||||
.map_or([].as_slice(), |x| x.as_slice())
|
.map_or([].as_slice(), |x| x.as_slice())
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|inputs| inputs.get(Input::INDEX))
|
.filter_map(|inputs| inputs.get(Input::INDEX))
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graph_craft::concrete;
|
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{NodeId, NodeNetwork};
|
use graph_craft::document::{NodeId, NodeNetwork};
|
||||||
use graph_craft::graphene_compiler::Compiler;
|
use graph_craft::graphene_compiler::Compiler;
|
||||||
use graph_craft::proto::GraphErrors;
|
use graph_craft::proto::GraphErrors;
|
||||||
use graph_craft::wasm_application_io::EditorPreferences;
|
use graph_craft::wasm_application_io::EditorPreferences;
|
||||||
|
use graph_craft::{ProtoNodeIdentifier, concrete};
|
||||||
use graphene_std::Context;
|
use graphene_std::Context;
|
||||||
use graphene_std::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
|
use graphene_std::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
|
||||||
use graphene_std::instances::Instance;
|
use graphene_std::instances::Instance;
|
||||||
@@ -46,7 +46,7 @@ pub struct NodeRuntime {
|
|||||||
inspect_state: Option<InspectState>,
|
inspect_state: Option<InspectState>,
|
||||||
|
|
||||||
/// Mapping of the fully-qualified node paths to their preprocessor substitutions.
|
/// Mapping of the fully-qualified node paths to their preprocessor substitutions.
|
||||||
substitutions: HashMap<String, DocumentNode>,
|
substitutions: HashMap<ProtoNodeIdentifier, DocumentNode>,
|
||||||
|
|
||||||
// TODO: Remove, it doesn't need to be persisted anymore
|
// TODO: Remove, it doesn't need to be persisted anymore
|
||||||
/// The current renders of the thumbnails for layer nodes.
|
/// The current renders of the thumbnails for layer nodes.
|
||||||
@@ -435,7 +435,7 @@ impl InspectState {
|
|||||||
|
|
||||||
let monitor_node = DocumentNode {
|
let monitor_node = DocumentNode {
|
||||||
inputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node
|
inputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node
|
||||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
|
||||||
manual_composition: Some(graph_craft::generic!(T)),
|
manual_composition: Some(graph_craft::generic!(T)),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@@ -172,9 +172,10 @@ impl EditorTestUtils {
|
|||||||
pub fn get_node<'a, T: InputAccessor<'a, DocumentNode>>(&'a self) -> impl Iterator<Item = T> + 'a {
|
pub fn get_node<'a, T: InputAccessor<'a, DocumentNode>>(&'a self) -> impl Iterator<Item = T> + 'a {
|
||||||
self.active_document()
|
self.active_document()
|
||||||
.network_interface
|
.network_interface
|
||||||
.iter_recursive()
|
.document_network()
|
||||||
.inspect(|node| println!("{:#?}", node.1.implementation))
|
.recursive_nodes()
|
||||||
.filter_map(move |(_, document)| T::new_with_source(document))
|
.inspect(|(_, node, _)| println!("{:#?}", node.implementation))
|
||||||
|
.filter_map(move |(_, document, _)| T::new_with_source(document))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn move_mouse(&mut self, x: f64, y: f64, modifier_keys: ModifierKeys, mouse_keys: MouseKeys) {
|
pub async fn move_mouse(&mut self, x: f64, y: f64, modifier_keys: ModifierKeys, mouse_keys: MouseKeys) {
|
||||||
@@ -300,7 +301,7 @@ pub trait FrontendMessageTestUtils {
|
|||||||
|
|
||||||
impl FrontendMessageTestUtils for FrontendMessage {
|
impl FrontendMessageTestUtils for FrontendMessage {
|
||||||
fn check_node_graph_error(&self) {
|
fn check_node_graph_error(&self) {
|
||||||
let FrontendMessage::UpdateNodeGraph { nodes, .. } = self else { return };
|
let FrontendMessage::UpdateNodeGraphNodes { nodes, .. } = self else { return };
|
||||||
|
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
if let Some(error) = &node.errors {
|
if let Some(error) = &node.errors {
|
||||||
|
|||||||
@@ -45,3 +45,19 @@ pub trait TransitiveChild: Into<Self::Parent> + Into<Self::TopParent> {
|
|||||||
pub trait Hint {
|
pub trait Hint {
|
||||||
fn hints(&self) -> HashMap<String, String>;
|
fn hints(&self) -> HashMap<String, String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait HierarchicalTree {
|
||||||
|
fn build_message_tree() -> DebugMessageTree;
|
||||||
|
|
||||||
|
fn message_handler_data_str() -> MessageData {
|
||||||
|
MessageData::new(String::new(), Vec::new(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message_handler_str() -> MessageData {
|
||||||
|
MessageData::new(String::new(), Vec::new(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path() -> &'static str {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MessageData {
|
||||||
|
name: String,
|
||||||
|
fields: Vec<(String, usize)>,
|
||||||
|
path: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageData {
|
||||||
|
pub fn new(name: String, fields: Vec<(String, usize)>, path: &'static str) -> MessageData {
|
||||||
|
MessageData { name, fields, path }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fields(&self) -> &Vec<(String, usize)> {
|
||||||
|
&self.fields
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> &'static str {
|
||||||
|
self.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DebugMessageTree {
|
||||||
|
name: String,
|
||||||
|
variants: Option<Vec<DebugMessageTree>>,
|
||||||
|
message_handler: Option<MessageData>,
|
||||||
|
message_handler_data: Option<MessageData>,
|
||||||
|
path: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugMessageTree {
|
||||||
|
pub fn new(name: &str) -> DebugMessageTree {
|
||||||
|
DebugMessageTree {
|
||||||
|
name: name.to_string(),
|
||||||
|
variants: None,
|
||||||
|
message_handler: None,
|
||||||
|
message_handler_data: None,
|
||||||
|
path: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_path(&mut self, path: &'static str) {
|
||||||
|
self.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_variant(&mut self, variant: DebugMessageTree) {
|
||||||
|
if let Some(variants) = &mut self.variants {
|
||||||
|
variants.push(variant);
|
||||||
|
} else {
|
||||||
|
self.variants = Some(vec![variant]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_message_handler_data_field(&mut self, message_handler_data: MessageData) {
|
||||||
|
self.message_handler_data = Some(message_handler_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_message_handler_field(&mut self, message_handler: MessageData) {
|
||||||
|
self.message_handler = Some(message_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> &'static str {
|
||||||
|
self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn variants(&self) -> Option<&Vec<DebugMessageTree>> {
|
||||||
|
self.variants.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn message_handler_data_fields(&self) -> Option<&MessageData> {
|
||||||
|
self.message_handler_data.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn message_handler_fields(&self) -> Option<&MessageData> {
|
||||||
|
self.message_handler.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_message_handler_data_fields(&self) -> bool {
|
||||||
|
match self.message_handler_data_fields() {
|
||||||
|
Some(_) => true,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_message_handler_fields(&self) -> bool {
|
||||||
|
match self.message_handler_fields() {
|
||||||
|
Some(_) => true,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
The Graphite frontend is a web app that provides the presentation for the editor. It displays the GUI based on state from the backend and provides users with interactive widgets that send updates to the backend, which is the source of truth for state information. The frontend is built out of reactive components using the [Svelte](https://svelte.dev/) framework. The backend is written in Rust and compiled to WebAssembly (WASM) to be run in the browser alongside the JS code.
|
The Graphite frontend is a web app that provides the presentation for the editor. It displays the GUI based on state from the backend and provides users with interactive widgets that send updates to the backend, which is the source of truth for state information. The frontend is built out of reactive components using the [Svelte](https://svelte.dev/) framework. The backend is written in Rust and compiled to WebAssembly (WASM) to be run in the browser alongside the JS code.
|
||||||
|
|
||||||
For lack of other options, the frontend is currently written as a web app. Maintaining web compatibility will always be a requirement, but the long-term plan is to port this code to a Rust-based native GUI framework, either written by the Rust community or created by our project if necessary. As a medium-term compromise, we may wrap the web-based frontend in a desktop webview windowing solution like Electron (probably not) or [Tauri](https://tauri.studio/) (probably).
|
For lack of other options, the frontend is currently written as a web app. Maintaining web compatibility will always be a requirement, but the long-term plan is to port this code to a Rust-based native GUI framework, either written by the Rust community or created by our project if necessary. As a medium-term compromise, we may wrap the web-based frontend in a desktop webview windowing solution like Electron (probably not) or [Tauri](https://tauri.app/) (probably).
|
||||||
|
|
||||||
## Bundled assets: `assets/`
|
## Bundled assets: `assets/`
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,6 @@
|
|||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use fern::colors::{Color, ColoredLevelConfig};
|
use fern::colors::{Color, ColoredLevelConfig};
|
||||||
use graphite_editor::application::Editor;
|
|
||||||
use graphite_editor::messages::prelude::*;
|
|
||||||
use graphite_editor::node_graph_executor::GraphRuntimeRequest;
|
|
||||||
use graphite_editor::node_graph_executor::NODE_RUNTIME;
|
|
||||||
use graphite_editor::node_graph_executor::*;
|
use graphite_editor::node_graph_executor::*;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onMount, tick } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import { cubicInOut } from "svelte/easing";
|
import { cubicInOut } from "svelte/easing";
|
||||||
import { fade } from "svelte/transition";
|
import { fade } from "svelte/transition";
|
||||||
|
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { Editor } from "@graphite/editor";
|
||||||
import type { Node } from "@graphite/messages";
|
import type { Node } from "@graphite/messages";
|
||||||
import type { FrontendNodeWire, FrontendNode, FrontendGraphInput, FrontendGraphOutput, FrontendGraphDataType, WirePath } from "@graphite/messages";
|
import type { FrontendNode, FrontendGraphInput, FrontendGraphOutput } from "@graphite/messages";
|
||||||
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
||||||
import type { IconName } from "@graphite/utility-functions/icons";
|
import type { IconName } from "@graphite/utility-functions/icons";
|
||||||
|
|
||||||
@@ -27,26 +27,13 @@
|
|||||||
const nodeGraph = getContext<NodeGraphState>("nodeGraph");
|
const nodeGraph = getContext<NodeGraphState>("nodeGraph");
|
||||||
|
|
||||||
let graph: HTMLDivElement | undefined;
|
let graph: HTMLDivElement | undefined;
|
||||||
let nodesContainer: HTMLDivElement | undefined;
|
|
||||||
|
|
||||||
// TODO: Using this not-complete code, or another better approach, make it so the dragged in-progress connector correctly handles showing/hiding the SVG shape of the connector caps
|
// Key value is node id + input/output index
|
||||||
// let wireInProgressFromLayerTop: bigint | undefined = undefined;
|
// Imports/Export are stored at a key value of 0
|
||||||
// let wireInProgressFromLayerBottom: bigint | undefined = undefined;
|
|
||||||
|
|
||||||
let nodeWirePaths: WirePath[] = [];
|
|
||||||
|
|
||||||
// TODO: Convert these arrays-of-arrays to a Map?
|
|
||||||
let inputs: SVGSVGElement[][] = [];
|
|
||||||
let outputs: SVGSVGElement[][] = [];
|
|
||||||
let nodeElements: HTMLDivElement[] = [];
|
|
||||||
|
|
||||||
$: watchNodes($nodeGraph.nodes);
|
|
||||||
|
|
||||||
$: gridSpacing = calculateGridSpacing($nodeGraph.transform.scale);
|
$: gridSpacing = calculateGridSpacing($nodeGraph.transform.scale);
|
||||||
$: dotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2;
|
$: dotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2;
|
||||||
|
|
||||||
$: wirePaths = createWirePaths($nodeGraph.wirePathInProgress, nodeWirePaths);
|
|
||||||
|
|
||||||
let inputElement: HTMLInputElement;
|
let inputElement: HTMLInputElement;
|
||||||
let hoveringImportIndex: number | undefined = undefined;
|
let hoveringImportIndex: number | undefined = undefined;
|
||||||
let hoveringExportIndex: number | undefined = undefined;
|
let hoveringExportIndex: number | undefined = undefined;
|
||||||
@@ -121,80 +108,6 @@
|
|||||||
return sparse;
|
return sparse;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createWirePaths(wirePathInProgress: WirePath | undefined, nodeWirePaths: WirePath[]): WirePath[] {
|
|
||||||
const maybeWirePathInProgress = wirePathInProgress ? [wirePathInProgress] : [];
|
|
||||||
return [...maybeWirePathInProgress, ...nodeWirePaths];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function watchNodes(nodes: Map<bigint, FrontendNode>) {
|
|
||||||
Array.from(nodes.keys()).forEach((_, index) => {
|
|
||||||
if (!inputs[index + 1]) inputs[index + 1] = [];
|
|
||||||
if (!outputs[index + 1]) outputs[index + 1] = [];
|
|
||||||
});
|
|
||||||
if (!inputs[0]) inputs[0] = [];
|
|
||||||
if (!outputs[0]) outputs[0] = [];
|
|
||||||
|
|
||||||
await refreshWires();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveWire(wire: FrontendNodeWire): { nodeOutput: SVGSVGElement; nodeInput: SVGSVGElement } | undefined {
|
|
||||||
// TODO: Avoid the linear search
|
|
||||||
const wireStartNodeIdIndex = Array.from($nodeGraph.nodes.keys()).findIndex((nodeId) => nodeId === (wire.wireStart as Node).nodeId);
|
|
||||||
let nodeOutputConnectors = outputs[wireStartNodeIdIndex + 1];
|
|
||||||
if (nodeOutputConnectors === undefined && (wire.wireStart as Node).nodeId === undefined) {
|
|
||||||
nodeOutputConnectors = outputs[0];
|
|
||||||
}
|
|
||||||
const indexOutput = Number(wire.wireStart.index);
|
|
||||||
const nodeOutput = nodeOutputConnectors?.[indexOutput] as SVGSVGElement | undefined;
|
|
||||||
if (nodeOutput === undefined) return undefined;
|
|
||||||
|
|
||||||
// TODO: Avoid the linear search
|
|
||||||
const wireEndNodeIdIndex = Array.from($nodeGraph.nodes.keys()).findIndex((nodeId) => nodeId === (wire.wireEnd as Node).nodeId);
|
|
||||||
let nodeInputConnectors = inputs[wireEndNodeIdIndex + 1] || undefined;
|
|
||||||
if (nodeInputConnectors === undefined && (wire.wireEnd as Node).nodeId === undefined) {
|
|
||||||
nodeInputConnectors = inputs[0];
|
|
||||||
}
|
|
||||||
const indexInput = Number(wire.wireEnd.index);
|
|
||||||
const nodeInput = nodeInputConnectors?.[indexInput] as SVGSVGElement | undefined;
|
|
||||||
if (nodeInput === undefined) return undefined;
|
|
||||||
|
|
||||||
return { nodeOutput, nodeInput };
|
|
||||||
}
|
|
||||||
|
|
||||||
function createWirePath(outputPort: SVGSVGElement, inputPort: SVGSVGElement, verticalOut: boolean, verticalIn: boolean, dashed: boolean, directNotGridAligned: boolean): WirePath {
|
|
||||||
const inputPortRect = inputPort.getBoundingClientRect();
|
|
||||||
const outputPortRect = outputPort.getBoundingClientRect();
|
|
||||||
|
|
||||||
const pathString = directNotGridAligned
|
|
||||||
? buildCurvedWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn)
|
|
||||||
: buildStraightWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn);
|
|
||||||
const dataType = (outputPort.getAttribute("data-datatype") as FrontendGraphDataType) || "General";
|
|
||||||
const thick = verticalIn && verticalOut;
|
|
||||||
|
|
||||||
return { pathString, dataType, thick, dashed };
|
|
||||||
}
|
|
||||||
|
|
||||||
async function refreshWires() {
|
|
||||||
await tick();
|
|
||||||
|
|
||||||
nodeWirePaths = $nodeGraph.wires.flatMap((wire) => {
|
|
||||||
// TODO: This call contains linear searches, which combined with the loop we're in, causes O(n^2) complexity as the graph grows
|
|
||||||
const resolvedWires = resolveWire(wire);
|
|
||||||
if (!resolvedWires) return [];
|
|
||||||
const { nodeOutput, nodeInput } = resolvedWires;
|
|
||||||
|
|
||||||
const wireStartNode = wire.wireStart.nodeId !== undefined ? $nodeGraph.nodes.get(wire.wireStart.nodeId) : undefined;
|
|
||||||
const wireStart = wireStartNode?.isLayer || false;
|
|
||||||
|
|
||||||
const wireEndNode = wire.wireEnd.nodeId !== undefined ? $nodeGraph.nodes.get(wire.wireEnd.nodeId) : undefined;
|
|
||||||
const wireEnd = (wireEndNode?.isLayer && Number(wire.wireEnd.index) === 0) || false;
|
|
||||||
|
|
||||||
return [createWirePath(nodeOutput, nodeInput, wireStart, wireEnd, wire.dashed, $nodeGraph.wiresDirectNotGridAligned)];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(refreshWires);
|
|
||||||
|
|
||||||
function nodeIcon(icon?: string): IconName {
|
function nodeIcon(icon?: string): IconName {
|
||||||
if (!icon) return "NodeNodes";
|
if (!icon) return "NodeNodes";
|
||||||
const iconMap: Record<string, IconName> = {
|
const iconMap: Record<string, IconName> = {
|
||||||
@@ -203,325 +116,6 @@
|
|||||||
return iconMap[icon] || "NodeNodes";
|
return iconMap[icon] || "NodeNodes";
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildStraightWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
|
|
||||||
if (!nodesContainer) return [];
|
|
||||||
|
|
||||||
const VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP = 1;
|
|
||||||
const LINE_WIDTH = 2;
|
|
||||||
|
|
||||||
// Calculate coordinates for input and output connectors
|
|
||||||
const inX = verticalIn ? inputBounds.x + inputBounds.width / 2 : inputBounds.x;
|
|
||||||
const inY = verticalIn ? inputBounds.y + inputBounds.height - VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : inputBounds.y + inputBounds.height / 2;
|
|
||||||
const outX = verticalOut ? outputBounds.x + outputBounds.width / 2 : outputBounds.x + outputBounds.width - 1;
|
|
||||||
const outY = verticalOut ? outputBounds.y + VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : outputBounds.y + outputBounds.height / 2;
|
|
||||||
|
|
||||||
// Adjust for scale
|
|
||||||
const containerBounds = nodesContainer.getBoundingClientRect();
|
|
||||||
const scale = $nodeGraph.transform.scale;
|
|
||||||
const inConnectorX = Math.round((inX - containerBounds.x) / scale);
|
|
||||||
const inConnectorY = Math.round((inY - containerBounds.y) / scale);
|
|
||||||
const outConnectorX = Math.round((outX - containerBounds.x) / scale);
|
|
||||||
const outConnectorY = Math.round((outY - containerBounds.y) / scale);
|
|
||||||
|
|
||||||
// Helper functions for calculating coordinates
|
|
||||||
const calculateMidX = () => (inConnectorX + outConnectorX) / 2 + (((inConnectorX + outConnectorX) / 2) % gridSpacing);
|
|
||||||
const calculateMidY = () => (inConnectorY + outConnectorY) / 2 + (((inConnectorY + outConnectorY) / 2) % gridSpacing);
|
|
||||||
const calculateMidYAlternate = () => (inConnectorY + outConnectorY) / 2 - (((inConnectorY + outConnectorY) / 2) % gridSpacing);
|
|
||||||
|
|
||||||
// Define X coordinate calculations
|
|
||||||
const x1 = () => outConnectorX;
|
|
||||||
const x2 = () => outConnectorX + gridSpacing;
|
|
||||||
const x3 = () => inConnectorX - 2 * gridSpacing;
|
|
||||||
const x4 = () => inConnectorX;
|
|
||||||
const x5 = () => inConnectorX - 2 * gridSpacing + LINE_WIDTH;
|
|
||||||
const x6 = () => outConnectorX + gridSpacing + LINE_WIDTH;
|
|
||||||
const x7 = () => outConnectorX + 2 * gridSpacing + LINE_WIDTH;
|
|
||||||
const x8 = () => inConnectorX + LINE_WIDTH;
|
|
||||||
const x9 = () => outConnectorX + 2 * gridSpacing;
|
|
||||||
const x10 = () => calculateMidX() + LINE_WIDTH;
|
|
||||||
const x11 = () => outConnectorX - gridSpacing;
|
|
||||||
const x12 = () => outConnectorX - 4 * gridSpacing;
|
|
||||||
const x13 = () => calculateMidX();
|
|
||||||
const x14 = () => inConnectorX + gridSpacing;
|
|
||||||
const x15 = () => inConnectorX - 4 * gridSpacing;
|
|
||||||
const x16 = () => inConnectorX + 8 * gridSpacing;
|
|
||||||
const x17 = () => calculateMidX() - 2 * LINE_WIDTH;
|
|
||||||
const x18 = () => outConnectorX + gridSpacing - 2 * LINE_WIDTH;
|
|
||||||
const x19 = () => outConnectorX - 2 * LINE_WIDTH;
|
|
||||||
const x20 = () => calculateMidX() - LINE_WIDTH;
|
|
||||||
|
|
||||||
// Define Y coordinate calculations
|
|
||||||
const y1 = () => outConnectorY;
|
|
||||||
const y2 = () => outConnectorY - gridSpacing;
|
|
||||||
const y3 = () => inConnectorY;
|
|
||||||
const y4 = () => outConnectorY - gridSpacing + 5.5 * LINE_WIDTH;
|
|
||||||
const y5 = () => inConnectorY - 2 * gridSpacing;
|
|
||||||
const y6 = () => outConnectorY + 4 * LINE_WIDTH;
|
|
||||||
const y7 = () => outConnectorY + 5 * LINE_WIDTH;
|
|
||||||
const y8 = () => outConnectorY - 2 * gridSpacing + 5.5 * LINE_WIDTH;
|
|
||||||
const y9 = () => outConnectorY + 6 * LINE_WIDTH;
|
|
||||||
const y10 = () => inConnectorY + 2 * gridSpacing;
|
|
||||||
const y111 = () => inConnectorY + gridSpacing + 6.5 * LINE_WIDTH;
|
|
||||||
const y12 = () => inConnectorY + gridSpacing - 5.5 * LINE_WIDTH;
|
|
||||||
const y13 = () => inConnectorY - gridSpacing;
|
|
||||||
const y14 = () => inConnectorY + gridSpacing;
|
|
||||||
const y15 = () => calculateMidY();
|
|
||||||
const y16 = () => calculateMidYAlternate();
|
|
||||||
|
|
||||||
// Helper function for constructing coordinate pairs
|
|
||||||
const construct = (...coords: [() => number, () => number][]) => coords.map(([x, y]) => ({ x: x(), y: y() }));
|
|
||||||
|
|
||||||
// Define wire path shapes that get used more than once
|
|
||||||
const wire1 = () => construct([x1, y1], [x1, y4], [x5, y4], [x5, y3], [x4, y3]);
|
|
||||||
const wire2 = () => construct([x1, y1], [x1, y16], [x3, y16], [x3, y3], [x4, y3]);
|
|
||||||
const wire3 = () => construct([x1, y1], [x1, y4], [x12, y4], [x12, y10], [x3, y10], [x3, y3], [x4, y3]);
|
|
||||||
const wire4 = () => construct([x1, y1], [x1, y4], [x13, y4], [x13, y10], [x3, y10], [x3, y3], [x4, y3]);
|
|
||||||
|
|
||||||
// `outConnector` point and `inConnector` point lying on the same horizontal grid line and `outConnector` point lies to the right of `inConnector` point
|
|
||||||
if (outConnectorY === inConnectorY && outConnectorX > inConnectorX && (verticalOut || !verticalIn)) return construct([x1, y1], [x2, y1], [x2, y2], [x3, y2], [x3, y3], [x4, y3]);
|
|
||||||
|
|
||||||
// Handle straight lines
|
|
||||||
if (outConnectorY === inConnectorY || (outConnectorX === inConnectorX && verticalOut)) return construct([x1, y1], [x4, y3]);
|
|
||||||
|
|
||||||
// Handle standard right-angle paths
|
|
||||||
// Start vertical, then horizontal
|
|
||||||
|
|
||||||
// `outConnector` point lies to the left of `inConnector` point
|
|
||||||
if (verticalOut && inConnectorX > outConnectorX) {
|
|
||||||
// `outConnector` point lies above `inConnector` point
|
|
||||||
if (outConnectorY < inConnectorY) {
|
|
||||||
// `outConnector` point lies on the vertical grid line 4 units to the left of `inConnector` point point
|
|
||||||
if (-4 * gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX < -3 * gridSpacing) return wire1();
|
|
||||||
|
|
||||||
// `outConnector` point lying on vertical grid lines 3 and 2 units to the left of `inConnector` point
|
|
||||||
if (-3 * gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= -1 * gridSpacing) {
|
|
||||||
if (-2 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= -1 * gridSpacing) return construct([x1, y1], [x1, y2], [x2, y2], [x2, y3], [x4, y3]);
|
|
||||||
|
|
||||||
if (-1 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 0 * gridSpacing) return construct([x1, y1], [x1, y4], [x6, y4], [x6, y3], [x4, y3]);
|
|
||||||
|
|
||||||
return construct([x1, y1], [x1, y4], [x7, y4], [x7, y5], [x3, y5], [x3, y3], [x4, y3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// `outConnector` point lying on vertical grid line 1 units to the left of `inConnector` point
|
|
||||||
if (-1 * gridSpacing < outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 0 * gridSpacing) {
|
|
||||||
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
|
|
||||||
if (-2 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= -1 * gridSpacing) return construct([x1, y6], [x2, y6], [x8, y3]);
|
|
||||||
|
|
||||||
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
|
|
||||||
if (-1 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 0 * gridSpacing) return construct([x1, y7], [x4, y3]);
|
|
||||||
|
|
||||||
return construct([x1, y1], [x1, y2], [x9, y2], [x9, y5], [x3, y5], [x3, y3], [x4, y3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return construct([x1, y1], [x1, y4], [x10, y4], [x10, y3], [x4, y3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// `outConnector` point lies below `inConnector` point
|
|
||||||
// `outConnector` point lying on vertical grid line 1 unit to the left of `inConnector` point
|
|
||||||
if (-1 * gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 0 * gridSpacing) {
|
|
||||||
// `outConnector` point lying on the horizontal grid lines 1 and 2 units below the `inConnector` point
|
|
||||||
if (0 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 2 * gridSpacing) construct([x1, y6], [x11, y6], [x11, y3], [x4, y3]);
|
|
||||||
|
|
||||||
return wire2();
|
|
||||||
}
|
|
||||||
|
|
||||||
return construct([x1, y1], [x1, y3], [x4, y3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// `outConnector` point lies to the right of `inConnector` point
|
|
||||||
if (verticalOut && inConnectorX <= outConnectorX) {
|
|
||||||
// `outConnector` point lying on any horizontal grid line above `inConnector` point
|
|
||||||
if (outConnectorY < inConnectorY) {
|
|
||||||
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
|
|
||||||
if (-2 * gridSpacing < outConnectorY - inConnectorY && outConnectorY - inConnectorY <= -1 * gridSpacing) return wire1();
|
|
||||||
|
|
||||||
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
|
|
||||||
if (-1 * gridSpacing < outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 0 * gridSpacing) return construct([x1, y1], [x1, y8], [x5, y8], [x5, y3], [x4, y3]);
|
|
||||||
|
|
||||||
// `outConnector` point lying on vertical grid lines 1 and 2 units to the right of `inConnector` point
|
|
||||||
if (gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 3 * gridSpacing) {
|
|
||||||
return construct([x1, y1], [x1, y4], [x9, y4], [x9, y5], [x3, y5], [x3, y3], [x4, y3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return construct([x1, y1], [x1, y4], [x10, y4], [x10, y5], [x5, y5], [x5, y3], [x4, y3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// `outConnector` point lies below `inConnector` point
|
|
||||||
if (outConnectorY - inConnectorY <= gridSpacing) {
|
|
||||||
// `outConnector` point lies on the horizontal grid line 1 unit below the `inConnector` Point
|
|
||||||
if (0 <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 13 * gridSpacing) return construct([x1, y9], [x3, y9], [x3, y3], [x4, y3]);
|
|
||||||
|
|
||||||
if (13 < outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 18 * gridSpacing) return wire3();
|
|
||||||
|
|
||||||
return wire4();
|
|
||||||
}
|
|
||||||
|
|
||||||
// `outConnector` point lies on the horizontal grid line 2 units below `outConnector` point
|
|
||||||
if (gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 2 * gridSpacing) {
|
|
||||||
if (0 <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 13 * gridSpacing) return construct([x1, y7], [x5, y7], [x5, y3], [x4, y3]);
|
|
||||||
|
|
||||||
if (13 < outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 18 * gridSpacing) return wire3();
|
|
||||||
|
|
||||||
return wire4();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0 to 4 units below the `outConnector` Point
|
|
||||||
if (outConnectorY - inConnectorY <= 4 * gridSpacing) return wire1();
|
|
||||||
|
|
||||||
return wire2();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start horizontal, then vertical
|
|
||||||
if (verticalIn) {
|
|
||||||
// when `outConnector` lies below `inConnector`
|
|
||||||
if (outConnectorY > inConnectorY) {
|
|
||||||
// `outConnectorX` lies to the left of `inConnectorX`
|
|
||||||
if (outConnectorX < inConnectorX) return construct([x1, y1], [x4, y1], [x4, y3]);
|
|
||||||
|
|
||||||
// `outConnectorX` lies to the right of `inConnectorX`
|
|
||||||
if (outConnectorY - inConnectorY <= gridSpacing) {
|
|
||||||
// `outConnector` point directly below `inConnector` point
|
|
||||||
if (0 <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= gridSpacing) return construct([x1, y1], [x14, y1], [x14, y2], [x4, y2], [x4, y3]);
|
|
||||||
|
|
||||||
// `outConnector` point lies below `inConnector` point and strictly to the right of `inConnector` point
|
|
||||||
return construct([x1, y1], [x2, y1], [x2, y111], [x4, y111], [x4, y3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return construct([x1, y1], [x2, y1], [x2, y2], [x4, y2], [x4, y3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// `outConnectorY` lies on or above the `inConnectorY` point
|
|
||||||
if (-6 * gridSpacing < inConnectorX - outConnectorX && inConnectorX - outConnectorX < 4 * gridSpacing) {
|
|
||||||
// edge case: `outConnector` point lying on vertical grid lines ranging from 4 units to left to 5 units to right of `inConnector` point
|
|
||||||
if (-1 * gridSpacing < inConnectorX - outConnectorX && inConnectorX - outConnectorX < 4 * gridSpacing) {
|
|
||||||
return construct([x1, y1], [x2, y1], [x2, y2], [x15, y2], [x15, y12], [x4, y12], [x4, y3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return construct([x1, y1], [x16, y1], [x16, y12], [x4, y12], [x4, y3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// left of edge case: `outConnector` point lying on vertical grid lines more than 4 units to left of `inConnector` point
|
|
||||||
if (4 * gridSpacing < inConnectorX - outConnectorX) return construct([x1, y1], [x17, y1], [x17, y12], [x4, y12], [x4, y3]);
|
|
||||||
|
|
||||||
// right of edge case: `outConnector` point lying on the vertical grid lines more than 5 units to right of `inConnector` point
|
|
||||||
if (6 * gridSpacing > inConnectorX - outConnectorX) return construct([x1, y1], [x18, y1], [x18, y12], [x4, y12], [x4, y3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Both horizontal - use horizontal middle point
|
|
||||||
// When `inConnector` point is one of the two closest diagonally opposite points
|
|
||||||
if (0 <= inConnectorX - outConnectorX && inConnectorX - outConnectorX <= gridSpacing && inConnectorY - outConnectorY >= -1 * gridSpacing && inConnectorY - outConnectorY <= gridSpacing) {
|
|
||||||
return construct([x19, y1], [x19, y3], [x4, y3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// When `inConnector` point lies on the horizontal line 1 unit above and below the `outConnector` point
|
|
||||||
if (-1 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= gridSpacing && outConnectorX > inConnectorX) {
|
|
||||||
// Horizontal line above `outConnectorY`
|
|
||||||
if (inConnectorY < outConnectorY) return construct([x1, y1], [x2, y1], [x2, y13], [x3, y13], [x3, y3], [x4, y3]);
|
|
||||||
|
|
||||||
// Horizontal line below `outConnectorY`
|
|
||||||
return construct([x1, y1], [x2, y1], [x2, y14], [x3, y14], [x3, y3], [x4, y3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// `outConnector` point to the right of `inConnector` point
|
|
||||||
if (outConnectorX > inConnectorX - gridSpacing) return construct([x1, y1], [x18, y1], [x18, y15], [x5, y15], [x5, y3], [x4, y3]);
|
|
||||||
|
|
||||||
// When `inConnector` point lies on the vertical grid line two units to the right of `outConnector` point
|
|
||||||
if (gridSpacing <= inConnectorX - outConnectorX && inConnectorX - outConnectorX <= 2 * gridSpacing) return construct([x1, y1], [x18, y1], [x18, y3], [x4, y3]);
|
|
||||||
|
|
||||||
return construct([x1, y1], [x20, y1], [x20, y3], [x4, y3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildStraightWirePathString(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): string {
|
|
||||||
const locations = buildStraightWirePathLocations(outputBounds, inputBounds, verticalOut, verticalIn);
|
|
||||||
if (locations.length === 0) return "[error]";
|
|
||||||
if (locations.length === 2) return `M${locations[0].x},${locations[0].y} L${locations[1].x},${locations[1].y}`;
|
|
||||||
|
|
||||||
const CORNER_RADIUS = 10;
|
|
||||||
|
|
||||||
// Create path with rounded corners
|
|
||||||
let path = `M${locations[0].x},${locations[0].y}`;
|
|
||||||
|
|
||||||
for (let i = 1; i < locations.length - 1; i++) {
|
|
||||||
const prev = locations[i - 1];
|
|
||||||
const curr = locations[i];
|
|
||||||
const next = locations[i + 1];
|
|
||||||
|
|
||||||
// Calculate corner points
|
|
||||||
const isVertical = curr.x === prev.x;
|
|
||||||
const cornerStart = {
|
|
||||||
x: curr.x + (isVertical ? 0 : prev.x < curr.x ? -CORNER_RADIUS : CORNER_RADIUS),
|
|
||||||
y: curr.y + (isVertical ? (prev.y < curr.y ? -CORNER_RADIUS : CORNER_RADIUS) : 0),
|
|
||||||
};
|
|
||||||
const cornerEnd = {
|
|
||||||
x: curr.x + (isVertical ? (next.x < curr.x ? -CORNER_RADIUS : CORNER_RADIUS) : 0),
|
|
||||||
y: curr.y + (isVertical ? 0 : next.y < curr.y ? -CORNER_RADIUS : CORNER_RADIUS),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add line to corner start, quadratic curve for corner, then continue to next point
|
|
||||||
path += ` L${cornerStart.x},${cornerStart.y}`;
|
|
||||||
path += ` Q${curr.x},${curr.y} ${cornerEnd.x},${cornerEnd.y}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
path += ` L${locations[locations.length - 1].x},${locations[locations.length - 1].y}`;
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildCurvedWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
|
|
||||||
if (!nodesContainer) return [];
|
|
||||||
|
|
||||||
const VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP = 1;
|
|
||||||
|
|
||||||
const containerBounds = nodesContainer.getBoundingClientRect();
|
|
||||||
|
|
||||||
const outX = verticalOut ? outputBounds.x + outputBounds.width / 2 : outputBounds.x + outputBounds.width - 1;
|
|
||||||
const outY = verticalOut ? outputBounds.y + VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : outputBounds.y + outputBounds.height / 2;
|
|
||||||
const outConnectorX = (outX - containerBounds.x) / $nodeGraph.transform.scale;
|
|
||||||
const outConnectorY = (outY - containerBounds.y) / $nodeGraph.transform.scale;
|
|
||||||
|
|
||||||
const inX = verticalIn ? inputBounds.x + inputBounds.width / 2 : inputBounds.x;
|
|
||||||
const inY = verticalIn ? inputBounds.y + inputBounds.height - VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : inputBounds.y + inputBounds.height / 2;
|
|
||||||
const inConnectorX = (inX - containerBounds.x) / $nodeGraph.transform.scale;
|
|
||||||
const inConnectorY = (inY - containerBounds.y) / $nodeGraph.transform.scale;
|
|
||||||
const horizontalGap = Math.abs(outConnectorX - inConnectorX);
|
|
||||||
const verticalGap = Math.abs(outConnectorY - inConnectorY);
|
|
||||||
|
|
||||||
const curveLength = 24;
|
|
||||||
const curveFalloffRate = curveLength * Math.PI * 2;
|
|
||||||
|
|
||||||
const horizontalCurveAmount = -(2 ** ((-10 * horizontalGap) / curveFalloffRate)) + 1;
|
|
||||||
const verticalCurveAmount = -(2 ** ((-10 * verticalGap) / curveFalloffRate)) + 1;
|
|
||||||
const horizontalCurve = horizontalCurveAmount * curveLength;
|
|
||||||
const verticalCurve = verticalCurveAmount * curveLength;
|
|
||||||
|
|
||||||
return [
|
|
||||||
{ x: outConnectorX, y: outConnectorY },
|
|
||||||
{ x: verticalOut ? outConnectorX : outConnectorX + horizontalCurve, y: verticalOut ? outConnectorY - verticalCurve : outConnectorY },
|
|
||||||
{ x: verticalIn ? inConnectorX : inConnectorX - horizontalCurve, y: verticalIn ? inConnectorY + verticalCurve : inConnectorY },
|
|
||||||
{ x: inConnectorX, y: inConnectorY },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildCurvedWirePathString(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): string {
|
|
||||||
const locations = buildCurvedWirePathLocations(outputBounds, inputBounds, verticalOut, verticalIn);
|
|
||||||
if (locations.length === 0) return "[error]";
|
|
||||||
|
|
||||||
const SMOOTHING = 0.5;
|
|
||||||
const delta01 = { x: (locations[1].x - locations[0].x) * SMOOTHING, y: (locations[1].y - locations[0].y) * SMOOTHING };
|
|
||||||
const delta23 = { x: (locations[3].x - locations[2].x) * SMOOTHING, y: (locations[3].y - locations[2].y) * SMOOTHING };
|
|
||||||
|
|
||||||
return `
|
|
||||||
M${locations[0].x},${locations[0].y}
|
|
||||||
L${locations[1].x},${locations[1].y}
|
|
||||||
C${locations[1].x + delta01.x},${locations[1].y + delta01.y}
|
|
||||||
${locations[2].x - delta23.x},${locations[2].y - delta23.y}
|
|
||||||
${locations[2].x},${locations[2].y}
|
|
||||||
L${locations[3].x},${locations[3].y}
|
|
||||||
`
|
|
||||||
.split("\n")
|
|
||||||
.map((line) => line.trim())
|
|
||||||
.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleLayerDisplay(displayAsLayer: boolean, toggleId: bigint) {
|
function toggleLayerDisplay(displayAsLayer: boolean, toggleId: bigint) {
|
||||||
let node = $nodeGraph.nodes.get(toggleId);
|
let node = $nodeGraph.nodes.get(toggleId);
|
||||||
if (node) editor.handle.setToNodeOrLayer(node.id, displayAsLayer);
|
if (node) editor.handle.setToNodeOrLayer(node.id, displayAsLayer);
|
||||||
@@ -719,19 +313,21 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Node connection wires -->
|
<!-- Thick vertical layer connection wires -->
|
||||||
<div class="wires" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
<div class="wires" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
||||||
<svg>
|
<svg>
|
||||||
{#each wirePaths as { pathString, dataType, thick, dashed }}
|
{#each $nodeGraph.wires.values() as map}
|
||||||
{#if thick}
|
{#each map.values() as { pathString, dataType, thick, dashed }}
|
||||||
<path
|
{#if thick}
|
||||||
d={pathString}
|
<path
|
||||||
style:--data-line-width={`${thick ? 8 : 2}px`}
|
d={pathString}
|
||||||
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
|
style:--data-line-width={"8px"}
|
||||||
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
|
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
|
||||||
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
|
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
|
||||||
/>
|
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
|
||||||
{/if}
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -749,7 +345,6 @@
|
|||||||
style:--data-color-dim={`var(--color-data-${outputMetadata.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${outputMetadata.dataType.toLowerCase()}-dim)`}
|
||||||
style:--offset-left={position.x / 24}
|
style:--offset-left={position.x / 24}
|
||||||
style:--offset-top={position.y / 24}
|
style:--offset-top={position.y / 24}
|
||||||
bind:this={outputs[0][index]}
|
|
||||||
>
|
>
|
||||||
<title>{`${dataTypeTooltip(outputMetadata)}\n\n${outputConnectedToText(outputMetadata)}`}</title>
|
<title>{`${dataTypeTooltip(outputMetadata)}\n\n${outputConnectedToText(outputMetadata)}`}</title>
|
||||||
{#if outputMetadata.connectedTo !== undefined}
|
{#if outputMetadata.connectedTo !== undefined}
|
||||||
@@ -823,7 +418,6 @@
|
|||||||
style:--data-color-dim={`var(--color-data-${inputMetadata.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${inputMetadata.dataType.toLowerCase()}-dim)`}
|
||||||
style:--offset-left={position.x / 24}
|
style:--offset-left={position.x / 24}
|
||||||
style:--offset-top={position.y / 24}
|
style:--offset-top={position.y / 24}
|
||||||
bind:this={inputs[0][index]}
|
|
||||||
>
|
>
|
||||||
<title>{`${dataTypeTooltip(inputMetadata)}\n\n${inputConnectedToText(inputMetadata)}`}</title>
|
<title>{`${dataTypeTooltip(inputMetadata)}\n\n${inputConnectedToText(inputMetadata)}`}</title>
|
||||||
{#if inputMetadata.connectedTo !== undefined}
|
{#if inputMetadata.connectedTo !== undefined}
|
||||||
@@ -887,14 +481,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Layers and nodes -->
|
<!-- Layers and nodes -->
|
||||||
<div
|
<div class="layers-and-nodes" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
||||||
class="layers-and-nodes"
|
|
||||||
style:transform-origin={`0 0`}
|
|
||||||
style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}
|
|
||||||
bind:this={nodesContainer}
|
|
||||||
>
|
|
||||||
<!-- Layers -->
|
<!-- Layers -->
|
||||||
{#each Array.from($nodeGraph.nodes.values()).flatMap((node, nodeIndex) => (node.isLayer ? [{ node, nodeIndex }] : [])) as { node, nodeIndex } (nodeIndex)}
|
{#each Array.from($nodeGraph.nodes)
|
||||||
|
.filter(([nodeId, node]) => node.isLayer && $nodeGraph.visibleNodes.has(nodeId))
|
||||||
|
.map(([_, node], nodeIndex) => ({ node, nodeIndex })) as { node, nodeIndex } (nodeIndex)}
|
||||||
{@const clipPathId = String(Math.random()).substring(2)}
|
{@const clipPathId = String(Math.random()).substring(2)}
|
||||||
{@const stackDataInput = node.exposedInputs[0]}
|
{@const stackDataInput = node.exposedInputs[0]}
|
||||||
{@const layerAreaWidth = $nodeGraph.layerWidths.get(node.id) || 8}
|
{@const layerAreaWidth = $nodeGraph.layerWidths.get(node.id) || 8}
|
||||||
@@ -916,7 +507,6 @@
|
|||||||
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
|
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
|
||||||
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
|
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
|
||||||
data-node={node.id}
|
data-node={node.id}
|
||||||
bind:this={nodeElements[nodeIndex]}
|
|
||||||
>
|
>
|
||||||
{#if node.errors}
|
{#if node.errors}
|
||||||
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
|
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
|
||||||
@@ -936,7 +526,6 @@
|
|||||||
data-datatype={node.primaryOutput.dataType}
|
data-datatype={node.primaryOutput.dataType}
|
||||||
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
|
||||||
bind:this={outputs[nodeIndex + 1][0]}
|
|
||||||
>
|
>
|
||||||
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
|
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
|
||||||
{#if node.primaryOutput.connectedTo.length > 0}
|
{#if node.primaryOutput.connectedTo.length > 0}
|
||||||
@@ -958,7 +547,6 @@
|
|||||||
data-datatype={node.primaryInput?.dataType}
|
data-datatype={node.primaryInput?.dataType}
|
||||||
style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`}
|
style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`}
|
||||||
bind:this={inputs[nodeIndex + 1][0]}
|
|
||||||
>
|
>
|
||||||
{#if node.primaryInput}
|
{#if node.primaryInput}
|
||||||
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
|
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
|
||||||
@@ -984,7 +572,6 @@
|
|||||||
data-datatype={stackDataInput.dataType}
|
data-datatype={stackDataInput.dataType}
|
||||||
style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
|
||||||
bind:this={inputs[nodeIndex + 1][1]}
|
|
||||||
>
|
>
|
||||||
<title>{`${dataTypeTooltip(stackDataInput)}\n\n${validTypesText(stackDataInput)}\n\n${inputConnectedToText(stackDataInput)}`}</title>
|
<title>{`${dataTypeTooltip(stackDataInput)}\n\n${validTypesText(stackDataInput)}\n\n${inputConnectedToText(stackDataInput)}`}</title>
|
||||||
{#if stackDataInput.connectedTo !== undefined}
|
{#if stackDataInput.connectedTo !== undefined}
|
||||||
@@ -1025,22 +612,35 @@
|
|||||||
<!-- Node connection wires -->
|
<!-- Node connection wires -->
|
||||||
<div class="wires">
|
<div class="wires">
|
||||||
<svg>
|
<svg>
|
||||||
{#each wirePaths as { pathString, dataType, thick, dashed }}\
|
{#each $nodeGraph.wires.values() as map}
|
||||||
{#if !thick}
|
{#each map.values() as { pathString, dataType, thick, dashed }}
|
||||||
<path
|
{#if !thick}
|
||||||
d={pathString}
|
<path
|
||||||
style:--data-line-width={`${thick ? 8 : 2}px`}
|
d={pathString}
|
||||||
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
|
style:--data-line-width={"2px"}
|
||||||
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
|
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
|
||||||
style:--data-dasharray={dashed ? "4" : undefined}
|
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
|
||||||
/>
|
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
|
||||||
{/if}
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if $nodeGraph.wirePathInProgress}
|
||||||
|
<path
|
||||||
|
d={$nodeGraph.wirePathInProgress?.pathString}
|
||||||
|
style:--data-line-width={`${$nodeGraph.wirePathInProgress.thick ? 8 : 2}px`}
|
||||||
|
style:--data-color={`var(--color-data-${$nodeGraph.wirePathInProgress.dataType.toLowerCase()})`}
|
||||||
|
style:--data-color-dim={`var(--color-data-${$nodeGraph.wirePathInProgress.dataType.toLowerCase()}-dim)`}
|
||||||
|
style:--data-dasharray={`3,${$nodeGraph.wirePathInProgress.dashed ? 2 : 0}`}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Nodes -->
|
<!-- Nodes -->
|
||||||
{#each Array.from($nodeGraph.nodes.values()).flatMap((node, nodeIndex) => (node.isLayer ? [] : [{ node, nodeIndex }])) as { node, nodeIndex } (nodeIndex)}
|
{#each Array.from($nodeGraph.nodes)
|
||||||
|
.filter(([nodeId, node]) => !node.isLayer && $nodeGraph.visibleNodes.has(nodeId))
|
||||||
|
.map(([_, node], nodeIndex) => ({ node, nodeIndex })) as { node, nodeIndex } (nodeIndex)}
|
||||||
{@const exposedInputsOutputs = zipWithUndefined(node.exposedInputs, node.exposedOutputs)}
|
{@const exposedInputsOutputs = zipWithUndefined(node.exposedInputs, node.exposedOutputs)}
|
||||||
{@const clipPathId = String(Math.random()).substring(2)}
|
{@const clipPathId = String(Math.random()).substring(2)}
|
||||||
{@const description = (node.reference && $nodeGraph.nodeDescriptions.get(node.reference)) || undefined}
|
{@const description = (node.reference && $nodeGraph.nodeDescriptions.get(node.reference)) || undefined}
|
||||||
@@ -1056,7 +656,6 @@
|
|||||||
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
|
||||||
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
|
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
|
||||||
data-node={node.id}
|
data-node={node.id}
|
||||||
bind:this={nodeElements[nodeIndex]}
|
|
||||||
>
|
>
|
||||||
{#if node.errors}
|
{#if node.errors}
|
||||||
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
|
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
|
||||||
@@ -1091,7 +690,6 @@
|
|||||||
data-datatype={node.primaryInput?.dataType}
|
data-datatype={node.primaryInput?.dataType}
|
||||||
style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
|
||||||
bind:this={inputs[nodeIndex + 1][0]}
|
|
||||||
>
|
>
|
||||||
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
|
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
|
||||||
{#if node.primaryInput.connectedTo !== undefined}
|
{#if node.primaryInput.connectedTo !== undefined}
|
||||||
@@ -1111,7 +709,6 @@
|
|||||||
data-datatype={secondary.dataType}
|
data-datatype={secondary.dataType}
|
||||||
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
||||||
bind:this={inputs[nodeIndex + 1][index + (node.primaryInput ? 1 : 0)]}
|
|
||||||
>
|
>
|
||||||
<title>{`${dataTypeTooltip(secondary)}\n\n${validTypesText(secondary)}\n\n${inputConnectedToText(secondary)}`}</title>
|
<title>{`${dataTypeTooltip(secondary)}\n\n${validTypesText(secondary)}\n\n${inputConnectedToText(secondary)}`}</title>
|
||||||
{#if secondary.connectedTo !== undefined}
|
{#if secondary.connectedTo !== undefined}
|
||||||
@@ -1134,7 +731,6 @@
|
|||||||
data-datatype={node.primaryOutput.dataType}
|
data-datatype={node.primaryOutput.dataType}
|
||||||
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
|
||||||
bind:this={outputs[nodeIndex + 1][0]}
|
|
||||||
>
|
>
|
||||||
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
|
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
|
||||||
{#if node.primaryOutput.connectedTo !== undefined}
|
{#if node.primaryOutput.connectedTo !== undefined}
|
||||||
@@ -1144,7 +740,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</svg>
|
</svg>
|
||||||
{/if}
|
{/if}
|
||||||
{#each node.exposedOutputs as secondary, outputIndex}
|
{#each node.exposedOutputs as secondary}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 8 8"
|
viewBox="0 0 8 8"
|
||||||
@@ -1153,7 +749,6 @@
|
|||||||
data-datatype={secondary.dataType}
|
data-datatype={secondary.dataType}
|
||||||
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
||||||
bind:this={outputs[nodeIndex + 1][outputIndex + (node.primaryOutput ? 1 : 0)]}
|
|
||||||
>
|
>
|
||||||
<title>{`${dataTypeTooltip(secondary)}\n\n${outputConnectedToText(secondary)}`}</title>
|
<title>{`${dataTypeTooltip(secondary)}\n\n${outputConnectedToText(secondary)}`}</title>
|
||||||
{#if secondary.connectedTo !== undefined}
|
{#if secondary.connectedTo !== undefined}
|
||||||
|
|||||||
@@ -7,13 +7,14 @@
|
|||||||
|
|
||||||
export let value: string;
|
export let value: string;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
|
export let tooltip: string | undefined = undefined;
|
||||||
|
|
||||||
function setValue(newValue: ReferencePoint) {
|
function setValue(newValue: ReferencePoint) {
|
||||||
dispatch("value", newValue);
|
dispatch("value", newValue);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="reference-point-input" class:disabled>
|
<div class="reference-point-input" class:disabled title={tooltip}>
|
||||||
<button on:click={() => setValue("TopLeft")} class="row-1 col-1" class:active={value === "TopLeft"} tabindex="-1" {disabled}><div /></button>
|
<button on:click={() => setValue("TopLeft")} class="row-1 col-1" class:active={value === "TopLeft"} tabindex="-1" {disabled}><div /></button>
|
||||||
<button on:click={() => setValue("TopCenter")} class="row-1 col-2" class:active={value === "TopCenter"} tabindex="-1" {disabled}><div /></button>
|
<button on:click={() => setValue("TopCenter")} class="row-1 col-2" class:active={value === "TopCenter"} tabindex="-1" {disabled}><div /></button>
|
||||||
<button on:click={() => setValue("TopRight")} class="row-1 col-3" class:active={value === "TopRight"} tabindex="-1" {disabled}><div /></button>
|
<button on:click={() => setValue("TopRight")} class="row-1 col-3" class:active={value === "TopRight"} tabindex="-1" {disabled}><div /></button>
|
||||||
|
|||||||
+27
-48
@@ -96,16 +96,21 @@ export class UpdateLayerWidths extends JsMessage {
|
|||||||
readonly hasLeftInputWire!: Map<bigint, boolean>;
|
readonly hasLeftInputWire!: Map<bigint, boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateNodeGraph extends JsMessage {
|
export class UpdateNodeGraphNodes extends JsMessage {
|
||||||
@Type(() => FrontendNode)
|
@Type(() => FrontendNode)
|
||||||
readonly nodes!: FrontendNode[];
|
readonly nodes!: FrontendNode[];
|
||||||
|
|
||||||
@Type(() => FrontendNodeWire)
|
|
||||||
readonly wires!: FrontendNodeWire[];
|
|
||||||
|
|
||||||
readonly wiresDirectNotGridAligned!: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UpdateVisibleNodes extends JsMessage {
|
||||||
|
readonly nodes!: bigint[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateNodeGraphWires extends JsMessage {
|
||||||
|
readonly wires!: WireUpdate[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ClearAllNodeGraphWires extends JsMessage {}
|
||||||
|
|
||||||
export class UpdateNodeGraphTransform extends JsMessage {
|
export class UpdateNodeGraphTransform extends JsMessage {
|
||||||
readonly transform!: NodeGraphTransform;
|
readonly transform!: NodeGraphTransform;
|
||||||
}
|
}
|
||||||
@@ -219,7 +224,7 @@ export class FrontendGraphInput {
|
|||||||
|
|
||||||
readonly description!: string;
|
readonly description!: string;
|
||||||
|
|
||||||
readonly resolvedType!: string | undefined;
|
readonly resolvedType!: string;
|
||||||
|
|
||||||
readonly validTypes!: string[];
|
readonly validTypes!: string[];
|
||||||
|
|
||||||
@@ -252,7 +257,7 @@ export class FrontendGraphOutput {
|
|||||||
|
|
||||||
readonly description!: string;
|
readonly description!: string;
|
||||||
|
|
||||||
readonly resolvedType!: string | undefined;
|
readonly resolvedType!: string;
|
||||||
|
|
||||||
@CreateInputConnectorArray
|
@CreateInputConnectorArray
|
||||||
connectedTo!: Node[];
|
connectedTo!: Node[];
|
||||||
@@ -297,44 +302,6 @@ export class FrontendNode {
|
|||||||
readonly uiOnly!: boolean;
|
readonly uiOnly!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreateOutputConnector = Transform(({ obj }) => {
|
|
||||||
if (obj.wireStart.export !== undefined) {
|
|
||||||
return { index: obj.wireStart.export };
|
|
||||||
} else if (obj.wireStart.import !== undefined) {
|
|
||||||
return { index: obj.wireStart.import };
|
|
||||||
} else {
|
|
||||||
if (obj.wireStart.node.inputIndex !== undefined) {
|
|
||||||
return { nodeId: obj.wireStart.node.nodeId, index: obj.wireStart.node.inputIndex };
|
|
||||||
} else {
|
|
||||||
return { nodeId: obj.wireStart.node.nodeId, index: obj.wireStart.node.outputIndex };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const CreateInputConnector = Transform(({ obj }) => {
|
|
||||||
if (obj.wireEnd.export !== undefined) {
|
|
||||||
return { index: obj.wireEnd.export };
|
|
||||||
} else if (obj.wireEnd.import !== undefined) {
|
|
||||||
return { index: obj.wireEnd.import };
|
|
||||||
} else {
|
|
||||||
if (obj.wireEnd.node.inputIndex !== undefined) {
|
|
||||||
return { nodeId: obj.wireEnd.node.nodeId, index: obj.wireEnd.node.inputIndex };
|
|
||||||
} else {
|
|
||||||
return { nodeId: obj.wireEnd.node.nodeId, index: obj.wireEnd.node.outputIndex };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export class FrontendNodeWire {
|
|
||||||
@CreateOutputConnector
|
|
||||||
readonly wireStart!: Node;
|
|
||||||
|
|
||||||
@CreateInputConnector
|
|
||||||
readonly wireEnd!: Node;
|
|
||||||
|
|
||||||
readonly dashed!: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FrontendNodeType {
|
export class FrontendNodeType {
|
||||||
readonly name!: string;
|
readonly name!: string;
|
||||||
|
|
||||||
@@ -356,6 +323,12 @@ export class WirePath {
|
|||||||
readonly dashed!: boolean;
|
readonly dashed!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class WireUpdate {
|
||||||
|
readonly id!: bigint;
|
||||||
|
readonly inputIndex!: number;
|
||||||
|
readonly wirePathUpdate!: WirePath | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export class IndexedDbDocumentDetails extends DocumentDetails {
|
export class IndexedDbDocumentDetails extends DocumentDetails {
|
||||||
@Transform(({ value }: { value: bigint }) => value.toString())
|
@Transform(({ value }: { value: bigint }) => value.toString())
|
||||||
id!: string;
|
id!: string;
|
||||||
@@ -1367,6 +1340,9 @@ export class ReferencePointInput extends WidgetProps {
|
|||||||
value!: ReferencePoint;
|
value!: ReferencePoint;
|
||||||
|
|
||||||
disabled!: boolean;
|
disabled!: boolean;
|
||||||
|
|
||||||
|
@Transform(({ value }: { value: string }) => value || undefined)
|
||||||
|
tooltip!: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// WIDGET
|
// WIDGET
|
||||||
@@ -1645,6 +1621,7 @@ type JSMessageFactory = (data: any, wasm: WebAssembly.Memory, handle: EditorHand
|
|||||||
type MessageMaker = typeof JsMessage | JSMessageFactory;
|
type MessageMaker = typeof JsMessage | JSMessageFactory;
|
||||||
|
|
||||||
export const messageMakers: Record<string, MessageMaker> = {
|
export const messageMakers: Record<string, MessageMaker> = {
|
||||||
|
ClearAllNodeGraphWires,
|
||||||
DisplayDialog,
|
DisplayDialog,
|
||||||
DisplayDialogDismiss,
|
DisplayDialogDismiss,
|
||||||
DisplayDialogPanic,
|
DisplayDialogPanic,
|
||||||
@@ -1700,10 +1677,12 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||||||
UpdateLayerWidths,
|
UpdateLayerWidths,
|
||||||
UpdateMenuBarLayout,
|
UpdateMenuBarLayout,
|
||||||
UpdateMouseCursor,
|
UpdateMouseCursor,
|
||||||
UpdateNodeGraph,
|
UpdateNodeGraphNodes,
|
||||||
|
UpdateVisibleNodes,
|
||||||
|
UpdateNodeGraphWires,
|
||||||
|
UpdateNodeGraphTransform,
|
||||||
UpdateNodeGraphControlBarLayout,
|
UpdateNodeGraphControlBarLayout,
|
||||||
UpdateNodeGraphSelection,
|
UpdateNodeGraphSelection,
|
||||||
UpdateNodeGraphTransform,
|
|
||||||
UpdateNodeThumbnail,
|
UpdateNodeThumbnail,
|
||||||
UpdateOpenDocumentsList,
|
UpdateOpenDocumentsList,
|
||||||
UpdatePropertyPanelSectionsLayout,
|
UpdatePropertyPanelSectionsLayout,
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import {
|
|||||||
type FrontendClickTargets,
|
type FrontendClickTargets,
|
||||||
type ContextMenuInformation,
|
type ContextMenuInformation,
|
||||||
type FrontendNode,
|
type FrontendNode,
|
||||||
type FrontendNodeWire as FrontendNodeWire,
|
|
||||||
type FrontendNodeType,
|
type FrontendNodeType,
|
||||||
type WirePath,
|
type WirePath,
|
||||||
|
ClearAllNodeGraphWires,
|
||||||
SendUIMetadata,
|
SendUIMetadata,
|
||||||
UpdateBox,
|
UpdateBox,
|
||||||
UpdateClickTargets,
|
UpdateClickTargets,
|
||||||
@@ -19,7 +19,9 @@ import {
|
|||||||
UpdateExportReorderIndex,
|
UpdateExportReorderIndex,
|
||||||
UpdateImportsExports,
|
UpdateImportsExports,
|
||||||
UpdateLayerWidths,
|
UpdateLayerWidths,
|
||||||
UpdateNodeGraph,
|
UpdateNodeGraphNodes,
|
||||||
|
UpdateVisibleNodes,
|
||||||
|
UpdateNodeGraphWires,
|
||||||
UpdateNodeGraphSelection,
|
UpdateNodeGraphSelection,
|
||||||
UpdateNodeGraphTransform,
|
UpdateNodeGraphTransform,
|
||||||
UpdateNodeThumbnail,
|
UpdateNodeThumbnail,
|
||||||
@@ -40,8 +42,9 @@ export function createNodeGraphState(editor: Editor) {
|
|||||||
addImport: undefined as { x: number; y: number } | undefined,
|
addImport: undefined as { x: number; y: number } | undefined,
|
||||||
addExport: undefined as { x: number; y: number } | undefined,
|
addExport: undefined as { x: number; y: number } | undefined,
|
||||||
nodes: new Map<bigint, FrontendNode>(),
|
nodes: new Map<bigint, FrontendNode>(),
|
||||||
wires: [] as FrontendNodeWire[],
|
visibleNodes: new Set<bigint>(),
|
||||||
wiresDirectNotGridAligned: false,
|
/// The index is the exposed input index. The exports have a first key value of u32::MAX.
|
||||||
|
wires: new Map<bigint, Map<number, WirePath>>(),
|
||||||
wirePathInProgress: undefined as WirePath | undefined,
|
wirePathInProgress: undefined as WirePath | undefined,
|
||||||
nodeDescriptions: new Map<string, string>(),
|
nodeDescriptions: new Map<string, string>(),
|
||||||
nodeTypes: [] as FrontendNodeType[],
|
nodeTypes: [] as FrontendNodeType[],
|
||||||
@@ -114,15 +117,42 @@ export function createNodeGraphState(editor: Editor) {
|
|||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// TODO: Add a way to only update the nodes that have changed
|
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphNodes, (updateNodeGraphNodes) => {
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateNodeGraph, (updateNodeGraph) => {
|
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.nodes.clear();
|
state.nodes.clear();
|
||||||
updateNodeGraph.nodes.forEach((node) => {
|
updateNodeGraphNodes.nodes.forEach((node) => {
|
||||||
state.nodes.set(node.id, node);
|
state.nodes.set(node.id, node);
|
||||||
});
|
});
|
||||||
state.wires = updateNodeGraph.wires;
|
return state;
|
||||||
state.wiresDirectNotGridAligned = updateNodeGraph.wiresDirectNotGridAligned;
|
});
|
||||||
|
});
|
||||||
|
editor.subscriptions.subscribeJsMessage(UpdateVisibleNodes, (updateVisibleNodes) => {
|
||||||
|
update((state) => {
|
||||||
|
state.visibleNodes = new Set<bigint>(updateVisibleNodes.nodes);
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphWires, (updateNodeWires) => {
|
||||||
|
update((state) => {
|
||||||
|
updateNodeWires.wires.forEach((wireUpdate) => {
|
||||||
|
let inputMap = state.wires.get(wireUpdate.id);
|
||||||
|
// If it doesn't exist, create it and set it in the outer map
|
||||||
|
if (!inputMap) {
|
||||||
|
inputMap = new Map();
|
||||||
|
state.wires.set(wireUpdate.id, inputMap);
|
||||||
|
}
|
||||||
|
if (wireUpdate.wirePathUpdate !== undefined) {
|
||||||
|
inputMap.set(wireUpdate.inputIndex, wireUpdate.wirePathUpdate);
|
||||||
|
} else {
|
||||||
|
inputMap.delete(wireUpdate.inputIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
editor.subscriptions.subscribeJsMessage(ClearAllNodeGraphWires, (_) => {
|
||||||
|
update((state) => {
|
||||||
|
state.wires.clear();
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const ALLOWED_LICENSES = [
|
|||||||
"BSD-3-Clause",
|
"BSD-3-Clause",
|
||||||
"BSL-1.0",
|
"BSL-1.0",
|
||||||
"CC0-1.0",
|
"CC0-1.0",
|
||||||
|
"CDLA-Permissive-2.0",
|
||||||
"ISC",
|
"ISC",
|
||||||
"MIT-0",
|
"MIT-0",
|
||||||
"MIT",
|
"MIT",
|
||||||
@@ -29,6 +30,7 @@ const ALLOWED_LICENSES = [
|
|||||||
"Unicode-3.0",
|
"Unicode-3.0",
|
||||||
"Unicode-DFS-2016",
|
"Unicode-DFS-2016",
|
||||||
"Zlib",
|
"Zlib",
|
||||||
|
"NCSA",
|
||||||
];
|
];
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
|
|||||||
@@ -604,6 +604,7 @@ impl EditorHandle {
|
|||||||
node_id: Some(id),
|
node_id: Some(id),
|
||||||
node_type,
|
node_type,
|
||||||
xy: Some((x / 24, y / 24)),
|
xy: Some((x / 24, y / 24)),
|
||||||
|
add_transaction: true,
|
||||||
};
|
};
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ mod test {
|
|||||||
blend_mode: BlendMode::Normal,
|
blend_mode: BlendMode::Normal,
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
BrushCache::new_proto(),
|
BrushCache::default(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(image.instance_ref_iter().next().unwrap().instance.width, 20);
|
assert_eq!(image.instance_ref_iter().next().unwrap().instance.width, 20);
|
||||||
|
|||||||
@@ -6,11 +6,16 @@ use graphene_core::raster_types::CPU;
|
|||||||
use graphene_core::raster_types::Raster;
|
use graphene_core::raster_types::Raster;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::sync::Arc;
|
use std::hash::Hasher;
|
||||||
use std::sync::Mutex;
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
|
// TODO: This is a temporary hack, be sure to not reuse this when the brush is being rewritten.
|
||||||
|
static NEXT_BRUSH_CACHE_IMPL_ID: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
struct BrushCacheImpl {
|
struct BrushCacheImpl {
|
||||||
|
unique_id: u64,
|
||||||
// The full previous input that was cached.
|
// The full previous input that was cached.
|
||||||
prev_input: Vec<BrushStroke>,
|
prev_input: Vec<BrushStroke>,
|
||||||
|
|
||||||
@@ -90,9 +95,29 @@ impl BrushCacheImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for BrushCacheImpl {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
unique_id: NEXT_BRUSH_CACHE_IMPL_ID.fetch_add(1, Ordering::SeqCst),
|
||||||
|
prev_input: Vec::new(),
|
||||||
|
background: Default::default(),
|
||||||
|
blended_image: Default::default(),
|
||||||
|
last_stroke_texture: Default::default(),
|
||||||
|
brush_texture_cache: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for BrushCacheImpl {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.unique_id == other.unique_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Hash for BrushCacheImpl {
|
impl Hash for BrushCacheImpl {
|
||||||
// Zero hash.
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {}
|
self.unique_id.hash(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
@@ -103,46 +128,26 @@ pub struct BrushPlan {
|
|||||||
pub first_stroke_point_skip: usize,
|
pub first_stroke_point_skip: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Default, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct BrushCache {
|
pub struct BrushCache(Arc<Mutex<BrushCacheImpl>>);
|
||||||
inner: Arc<Mutex<BrushCacheImpl>>,
|
|
||||||
proto: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for BrushCache {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new_proto()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A bit of a cursed implementation to work around the current node system.
|
// A bit of a cursed implementation to work around the current node system.
|
||||||
// The original object is a 'prototype' that when cloned gives you a independent
|
// The original object is a 'prototype' that when cloned gives you a independent
|
||||||
// new object. Any further clones however are all the same underlying cache object.
|
// new object. Any further clones however are all the same underlying cache object.
|
||||||
impl Clone for BrushCache {
|
impl Clone for BrushCache {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
if self.proto {
|
Self(Arc::new(Mutex::new(self.0.lock().unwrap().clone())))
|
||||||
let inner_val = self.inner.lock().unwrap();
|
|
||||||
Self {
|
|
||||||
inner: Arc::new(Mutex::new(inner_val.clone())),
|
|
||||||
proto: false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Self {
|
|
||||||
inner: Arc::clone(&self.inner),
|
|
||||||
proto: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for BrushCache {
|
impl PartialEq for BrushCache {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
if Arc::ptr_eq(&self.inner, &other.inner) {
|
if Arc::ptr_eq(&self.0, &other.0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let s = self.inner.lock().unwrap();
|
let s = self.0.lock().unwrap();
|
||||||
let o = other.inner.lock().unwrap();
|
let o = other.0.lock().unwrap();
|
||||||
|
|
||||||
*s == *o
|
*s == *o
|
||||||
}
|
}
|
||||||
@@ -150,35 +155,28 @@ impl PartialEq for BrushCache {
|
|||||||
|
|
||||||
impl Hash for BrushCache {
|
impl Hash for BrushCache {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.inner.lock().unwrap().hash(state);
|
self.0.lock().unwrap().hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BrushCache {
|
impl BrushCache {
|
||||||
pub fn new_proto() -> Self {
|
|
||||||
Self {
|
|
||||||
inner: Default::default(),
|
|
||||||
proto: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compute_brush_plan(&self, background: Instance<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan {
|
pub fn compute_brush_plan(&self, background: Instance<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan {
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.0.lock().unwrap();
|
||||||
inner.compute_brush_plan(background, input)
|
inner.compute_brush_plan(background, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: Instance<Raster<CPU>>, last_stroke_texture: Instance<Raster<CPU>>) {
|
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: Instance<Raster<CPU>>, last_stroke_texture: Instance<Raster<CPU>>) {
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.0.lock().unwrap();
|
||||||
inner.cache_results(input, blended_image, last_stroke_texture)
|
inner.cache_results(input, blended_image, last_stroke_texture)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_cached_brush(&self, style: &BrushStyle) -> Option<Raster<CPU>> {
|
pub fn get_cached_brush(&self, style: &BrushStyle) -> Option<Raster<CPU>> {
|
||||||
let inner = self.inner.lock().unwrap();
|
let inner = self.0.lock().unwrap();
|
||||||
inner.brush_texture_cache.get(style).cloned()
|
inner.brush_texture_cache.get(style).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store_brush(&self, style: BrushStyle, brush: Raster<CPU>) {
|
pub fn store_brush(&self, style: BrushStyle, brush: Raster<CPU>) {
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.0.lock().unwrap();
|
||||||
inner.brush_texture_cache.insert(style, brush);
|
inner.brush_texture_cache.insert(style, brush);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ pub struct ContextImpl<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ContextImpl<'a> {
|
impl<'a> ContextImpl<'a> {
|
||||||
pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl (Borrow<[DynRef<'f>]>)>) -> ContextImpl<'f>
|
pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl Borrow<[DynRef<'f>]>>) -> ContextImpl<'f>
|
||||||
where
|
where
|
||||||
'a: 'f,
|
'a: 'f,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ pub mod debug;
|
|||||||
pub mod extract_xy;
|
pub mod extract_xy;
|
||||||
pub mod generic;
|
pub mod generic;
|
||||||
pub mod gradient;
|
pub mod gradient;
|
||||||
mod graphic_element;
|
pub mod graphic_element;
|
||||||
pub mod instances;
|
pub mod instances;
|
||||||
pub mod logic;
|
pub mod logic;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
@@ -35,7 +35,7 @@ pub use blending::*;
|
|||||||
pub use context::*;
|
pub use context::*;
|
||||||
pub use ctor;
|
pub use ctor;
|
||||||
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
|
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
|
||||||
pub use graphic_element::*;
|
pub use graphic_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable};
|
||||||
pub use memo::MemoHash;
|
pub use memo::MemoHash;
|
||||||
pub use num_traits;
|
pub use num_traits;
|
||||||
pub use raster::Color;
|
pub use raster::Color;
|
||||||
@@ -161,7 +161,7 @@ where
|
|||||||
|
|
||||||
pub trait NodeInputDecleration {
|
pub trait NodeInputDecleration {
|
||||||
const INDEX: usize;
|
const INDEX: usize;
|
||||||
fn identifier() -> &'static str;
|
fn identifier() -> ProtoNodeIdentifier;
|
||||||
type Result;
|
type Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::Color;
|
|||||||
use crate::GraphicElement;
|
use crate::GraphicElement;
|
||||||
use crate::GraphicGroupTable;
|
use crate::GraphicGroupTable;
|
||||||
use crate::gradient::GradientStops;
|
use crate::gradient::GradientStops;
|
||||||
|
use crate::graphene_core::registry::types::TextArea;
|
||||||
use crate::raster_types::{CPU, GPU, RasterDataTable};
|
use crate::raster_types::{CPU, GPU, RasterDataTable};
|
||||||
use crate::vector::VectorDataTable;
|
use crate::vector::VectorDataTable;
|
||||||
use crate::{Context, Ctx};
|
use crate::{Context, Ctx};
|
||||||
@@ -14,12 +15,12 @@ fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f6
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Text"))]
|
#[node_macro::node(category("Text"))]
|
||||||
fn string_concatenate(_: impl Ctx, #[implementations(String)] first: String, #[implementations(String)] second: String) -> String {
|
fn string_concatenate(_: impl Ctx, #[implementations(String)] first: String, second: TextArea) -> String {
|
||||||
first.clone() + &second
|
first.clone() + &second
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Text"))]
|
#[node_macro::node(category("Text"))]
|
||||||
fn string_replace(_: impl Ctx, #[implementations(String)] string: String, from: String, to: String) -> String {
|
fn string_replace(_: impl Ctx, #[implementations(String)] string: String, from: TextArea, to: TextArea) -> String {
|
||||||
string.replace(&from, &to)
|
string.replace(&from, &to)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::{Node, WasmNotSend};
|
|||||||
use dyn_any::DynFuture;
|
use dyn_any::DynFuture;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::hash::DefaultHasher;
|
use std::hash::DefaultHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
@@ -49,6 +50,10 @@ impl<T, CachedNode> MemoNode<T, CachedNode> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod memo {
|
||||||
|
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MemoNode");
|
||||||
|
}
|
||||||
|
|
||||||
/// Caches the output of a given Node and acts as a proxy.
|
/// Caches the output of a given Node and acts as a proxy.
|
||||||
/// In contrast to the regular `MemoNode`. This node ignores all input.
|
/// In contrast to the regular `MemoNode`. This node ignores all input.
|
||||||
/// Using this node might result in the document not updating properly,
|
/// Using this node might result in the document not updating properly,
|
||||||
@@ -98,6 +103,10 @@ impl<T, I, CachedNode> ImpureMemoNode<I, T, CachedNode> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod impure_memo {
|
||||||
|
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode");
|
||||||
|
}
|
||||||
|
|
||||||
/// Stores both what a node was called with and what it returned.
|
/// Stores both what a node was called with and what it returned.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct IORecord<I, O> {
|
pub struct IORecord<I, O> {
|
||||||
@@ -142,7 +151,10 @@ impl<I, T, N> MonitorNode<I, T, N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::hash::{Hash, Hasher};
|
pub mod monitor {
|
||||||
|
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MonitorNode");
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
pub struct MemoHash<T: Hash> {
|
pub struct MemoHash<T: Hash> {
|
||||||
hash: u64,
|
hash: u64,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{Node, NodeIO, NodeIOTypes, Type, WasmNotSend};
|
use crate::{Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend};
|
||||||
use dyn_any::{DynAny, StaticType};
|
use dyn_any::{DynAny, StaticType};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -30,6 +30,8 @@ pub mod types {
|
|||||||
pub type Resolution = glam::UVec2;
|
pub type Resolution = glam::UVec2;
|
||||||
/// DVec2 with px unit
|
/// DVec2 with px unit
|
||||||
pub type PixelSize = glam::DVec2;
|
pub type PixelSize = glam::DVec2;
|
||||||
|
/// String with one or more than one line
|
||||||
|
pub type TextArea = String;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translation struct between macro and definition
|
// Translation struct between macro and definition
|
||||||
@@ -101,11 +103,11 @@ pub enum RegistryValueSource {
|
|||||||
Scope(&'static str),
|
Scope(&'static str),
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeRegistry = LazyLock<Mutex<HashMap<String, Vec<(NodeConstructor, NodeIOTypes)>>>>;
|
type NodeRegistry = LazyLock<Mutex<HashMap<ProtoNodeIdentifier, Vec<(NodeConstructor, NodeIOTypes)>>>>;
|
||||||
|
|
||||||
pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::new()));
|
pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
pub static NODE_METADATA: LazyLock<Mutex<HashMap<String, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
|
pub static NODE_METADATA: LazyLock<Mutex<HashMap<ProtoNodeIdentifier, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>;
|
pub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>;
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ async fn transform<T: 'n + 'static>(
|
|||||||
rotate: f64,
|
rotate: f64,
|
||||||
scale: DVec2,
|
scale: DVec2,
|
||||||
skew: DVec2,
|
skew: DVec2,
|
||||||
_pivot: DVec2,
|
|
||||||
) -> Instances<T> {
|
) -> Instances<T> {
|
||||||
let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]);
|
let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]);
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user