diff --git a/desktop/assets/rs.graphite.Graphite.desktop b/desktop/assets/art.graphite.Graphite.desktop similarity index 100% rename from desktop/assets/rs.graphite.Graphite.desktop rename to desktop/assets/art.graphite.Graphite.desktop diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index deb50b6ab9..b6bc9b63ae 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -3,8 +3,7 @@ use std::path::PathBuf; use super::utility_types::misc::{GroupFolderType, SnappingState}; use crate::messages::input_mapper::utility_types::input_keyboard::Key; use crate::messages::portfolio::document::data_panel::DataPanelMessage; -use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; -use crate::messages::portfolio::document::overlays::utility_types::OverlaysType; +use crate::messages::portfolio::document::overlays::utility_types::{OverlayContext, OverlaysType}; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GridSnapping}; use crate::messages::portfolio::utility_types::PanelType; diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 4f527cae94..9898a95b93 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1,6 +1,5 @@ use super::node_graph::document_node_definitions; use super::node_graph::utility_types::Transform; -use super::overlays::utility_types::Pivot; use super::utility_types::error::EditorError; use super::utility_types::misc::{GroupFolderType, SNAP_FUNCTIONS_FOR_BOUNDING_BOXES, SNAP_FUNCTIONS_FOR_PATHS, SnappingOptions, SnappingState}; use super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus}; @@ -14,7 +13,7 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Transf use crate::messages::portfolio::document::node_graph::NodeGraphMessageContext; use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType; use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options}; -use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings}; +use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings, Pivot}; use crate::messages::portfolio::document::properties_panel::properties_panel_message_handler::PropertiesPanelMessageContext; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, PTZ}; diff --git a/editor/src/messages/portfolio/document/overlays/mod.rs b/editor/src/messages/portfolio/document/overlays/mod.rs index f672374cf6..4445dbfe84 100644 --- a/editor/src/messages/portfolio/document/overlays/mod.rs +++ b/editor/src/messages/portfolio/document/overlays/mod.rs @@ -2,8 +2,17 @@ pub mod grid_overlays; mod overlays_message; mod overlays_message_handler; pub mod utility_functions; -#[cfg_attr(not(target_family = "wasm"), path = "utility_types_vello.rs")] -pub mod utility_types; +// Native (non‑wasm) +#[cfg(not(target_family = "wasm"))] +pub mod utility_types_native; +#[cfg(not(target_family = "wasm"))] +pub use utility_types_native as utility_types; + +// WebAssembly +#[cfg(target_family = "wasm")] +pub mod utility_types_web; +#[cfg(target_family = "wasm")] +pub use utility_types_web as utility_types; #[doc(inline)] pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant}; diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index da9dc09fca..a666cfe2c3 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -6,9 +6,11 @@ use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerSta use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler}; use glam::{DAffine2, DVec2}; use graphene_std::subpath::{Bezier, BezierHandles}; +use graphene_std::text::{Font, FontCache, TextAlign, TextContext, TypesettingConfig}; use graphene_std::vector::misc::ManipulatorPointId; use graphene_std::vector::{PointId, SegmentId, Vector}; use std::collections::HashMap; +use std::sync::{LazyLock, Mutex}; use wasm_bindgen::JsCast; pub fn overlay_canvas_element() -> Option { @@ -218,3 +220,35 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: & } } } + +// Global lazy initialized font cache and text context +pub static GLOBAL_FONT_CACHE: LazyLock = LazyLock::new(|| { + let mut font_cache = FontCache::default(); + // Initialize with the hardcoded font used by overlay text + const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf"); + let font = Font::new("Source Sans Pro".to_string(), "Regular".to_string()); + font_cache.insert(font, String::new(), FONT_DATA.to_vec()); + font_cache +}); + +pub static GLOBAL_TEXT_CONTEXT: LazyLock> = LazyLock::new(|| Mutex::new(TextContext::default())); + +pub fn text_width(text: &str, font_size: f64) -> f64 { + let typesetting = TypesettingConfig { + font_size, + line_height_ratio: 1.2, + character_spacing: 0.0, + max_width: None, + max_height: None, + tilt: 0.0, + align: TextAlign::Left, + }; + + // Load Source Sans Pro font data + // TODO: Grab this from the node_modules folder (either with `include_bytes!` or ideally at runtime) instead of checking the font file into the repo. + // TODO: And maybe use the WOFF2 version (if it's supported) for its smaller, compressed file size. + let font = Font::new("Source Sans Pro".to_string(), "Regular".to_string()); + let mut text_context = GLOBAL_TEXT_CONTEXT.lock().expect("Failed to lock global text context"); + let bounds = text_context.bounding_box(text, &font, &GLOBAL_FONT_CACHE, typesetting, false); + bounds.x +} diff --git a/editor/src/messages/portfolio/document/overlays/utility_types_vello.rs b/editor/src/messages/portfolio/document/overlays/utility_types_native.rs similarity index 96% rename from editor/src/messages/portfolio/document/overlays/utility_types_vello.rs rename to editor/src/messages/portfolio/document/overlays/utility_types_native.rs index 4a0c50b0e3..3a0b40ce90 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types_vello.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types_native.rs @@ -3,6 +3,7 @@ use crate::consts::{ COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_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, RESIZE_HANDLE_SIZE, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE, }; +use crate::messages::portfolio::document::overlays::utility_functions::{GLOBAL_FONT_CACHE, GLOBAL_TEXT_CONTEXT}; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::prelude::Message; use crate::messages::prelude::ViewportMessageHandler; @@ -13,30 +14,17 @@ use graphene_std::Color; use graphene_std::math::quad::Quad; use graphene_std::subpath::{self, Subpath}; use graphene_std::table::Table; -use graphene_std::text::TextContext; -use graphene_std::text::{Font, FontCache, TextAlign, TypesettingConfig}; +use graphene_std::text::{Font, TextAlign, TypesettingConfig}; use graphene_std::vector::click_target::ClickTargetType; use graphene_std::vector::misc::point_to_dvec2; use graphene_std::vector::{PointId, SegmentId, Vector}; use kurbo::{self, BezPath, ParamCurve}; use kurbo::{Affine, PathSeg}; use std::collections::HashMap; -use std::sync::{Arc, LazyLock, Mutex, MutexGuard}; +use std::sync::{Arc, Mutex, MutexGuard}; use vello::Scene; use vello::peniko; -// Global lazy initialized font cache and text context -static GLOBAL_FONT_CACHE: LazyLock = LazyLock::new(|| { - let mut font_cache = FontCache::default(); - // Initialize with the hardcoded font used by overlay text - const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf"); - let font = Font::new("Source Sans Pro".to_string(), "Regular".to_string()); - font_cache.insert(font, String::new(), FONT_DATA.to_vec()); - font_cache -}); - -static GLOBAL_TEXT_CONTEXT: LazyLock> = LazyLock::new(|| Mutex::new(TextContext::default())); - pub type OverlayProvider = fn(OverlayContext) -> Message; pub fn empty_provider() -> OverlayProvider { @@ -393,10 +381,6 @@ impl OverlayContext { self.internal().fill_path_pattern(subpaths, transform, color); } - pub fn get_width(&self, text: &str) -> f64 { - self.internal().get_width(text) - } - pub fn text(&self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) { let mut internal = self.internal(); internal.text(text, font_color, background_color, transform, padding, pivot); @@ -1034,29 +1018,6 @@ impl OverlayContextInternal { self.scene.fill(peniko::Fill::NonZero, self.get_transform(), &brush, None, &path); } - fn get_width(&mut self, text: &str) -> f64 { - // Use the actual text-to-path system to get precise text width - const FONT_SIZE: f64 = 12.0; - - let typesetting = TypesettingConfig { - font_size: FONT_SIZE, - line_height_ratio: 1.2, - character_spacing: 0.0, - max_width: None, - max_height: None, - tilt: 0.0, - align: TextAlign::Left, - }; - - // Load Source Sans Pro font data - // TODO: Grab this from the node_modules folder (either with `include_bytes!` or ideally at runtime) instead of checking the font file into the repo. - // TODO: And maybe use the WOFF2 version (if it's supported) for its smaller, compressed file size. - let font = Font::new("Source Sans Pro".to_string(), "Regular".to_string()); - let mut text_context = GLOBAL_TEXT_CONTEXT.lock().expect("Failed to lock global text context"); - let bounds = text_context.bounding_box(text, &font, &GLOBAL_FONT_CACHE, typesetting, false); - bounds.x - } - fn text(&mut self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) { // Use the proper text-to-path system for accurate text rendering const FONT_SIZE: f64 = 12.0; diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types_web.rs similarity index 99% rename from editor/src/messages/portfolio/document/overlays/utility_types.rs rename to editor/src/messages/portfolio/document/overlays/utility_types_web.rs index e950c2b185..0800c65cfe 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types_web.rs @@ -962,10 +962,6 @@ impl OverlayContext { self.render_context.fill(); } - pub fn get_width(&self, text: &str) -> f64 { - self.render_context.measure_text(text).expect("Failed to measure text dimensions").width() - } - pub fn text(&self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) { let metrics = self.render_context.measure_text(text).expect("Failed to measure the text dimensions"); let x = match pivot[0] { diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index ff75d7bbde..8485d36d67 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -9,6 +9,7 @@ use crate::consts::{EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP, EXPORTS_TO_TOP_EDGE_PIXEL_G use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; use crate::messages::portfolio::document::node_graph::document_node_definitions::{DocumentNodeDefinition, resolve_document_node_type}; use crate::messages::portfolio::document::node_graph::utility_types::{Direction, FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput}; +use crate::messages::portfolio::document::overlays::utility_functions::text_width; use crate::messages::portfolio::document::utility_types::network_interface::resolved_types::ResolvedDocumentNodeTypes; use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire}; use crate::messages::tool::common_functionality::graph_modification_utils; @@ -1052,7 +1053,11 @@ impl NodeNetworkInterface { log::error!("Could not get downstream_connectors in primary_output_connected_to_layer"); return false; }; - let downstream_nodes = downstream_connectors.iter().filter_map(|connector| connector.node_id()).collect::>(); + + let downstream_nodes = downstream_connectors + .iter() + .filter_map(|connector| connector.node_id().filter(|_| connector.input_index() == 0)) + .collect::>(); downstream_nodes.iter().any(|node_id| self.is_layer(node_id, network_path)) } @@ -1314,57 +1319,6 @@ impl NodeNetworkInterface { .any(|id| id == potentially_upstream_node) } - #[cfg(not(target_family = "wasm"))] - fn text_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option { - warn!("Failed to find width of {node_id:#?} in network_path {network_path:?} due to non-wasm arch"); - Some(0.) - } - - #[cfg(target_family = "wasm")] - fn text_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option { - let document = web_sys::window().unwrap().document().unwrap(); - let div = match document.create_element("div") { - Ok(div) => div, - Err(err) => { - log::error!("Error creating div: {:?}", err); - return None; - } - }; - - // Set the div's style to make it offscreen and single line - match div.set_attribute("style", "position: absolute; top: -9999px; left: -9999px; white-space: nowrap;") { - Err(err) => { - log::error!("Error setting attribute: {:?}", err); - return None; - } - _ => {} - }; - - let name = self.display_name(node_id, network_path); - - div.set_text_content(Some(&name)); - - // Append the div to the document body - match document.body().unwrap().append_child(&div) { - Err(err) => { - log::error!("Error setting adding child to document {:?}", err); - return None; - } - _ => {} - }; - - // Measure the width - let text_width = div.get_bounding_client_rect().width(); - - // Remove the div from the document - match document.body().unwrap().remove_child(&div) { - Err(_) => log::error!("Could not remove child when rendering text"), - _ => {} - }; - - Some(text_width) - } - pub fn from_old_network(old_network: OldNodeNetwork) -> Self { let mut node_network = NodeNetwork::default(); let mut network_metadata = NodeNetworkMetadata::default(); @@ -2121,19 +2075,19 @@ impl NodeNetworkInterface { } pub fn load_layer_width(&mut self, node_id: &NodeId, network_path: &[NodeId]) { + const GAP_WIDTH: f64 = 8.; + const FONT_SIZE: f64 = 14.; let left_thumbnail_padding = GRID_SIZE as f64 / 2.; let thumbnail_width = 3. * GRID_SIZE as f64; - let gap_width = 8.; - let text_width = self.text_width(node_id, network_path).unwrap_or_else(|| { - log::error!("Could not get text width for node {node_id}"); - 0. - }); + let layer_text = self.display_name(node_id, network_path); + + let text_width = text_width(&layer_text, FONT_SIZE); let grip_padding = 4.; let grip_width = 8.; let icon_overhang_width = GRID_SIZE as f64 / 2.; - let layer_width_pixels = left_thumbnail_padding + thumbnail_width + gap_width + text_width + grip_padding + grip_width + icon_overhang_width; + let layer_width_pixels = left_thumbnail_padding + thumbnail_width + GAP_WIDTH + text_width + grip_padding + grip_width + icon_overhang_width; let layer_width = ((layer_width_pixels / 24.).ceil() as u32).max(8); let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/sweep_angle_gizmo.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/sweep_angle_gizmo.rs index 68d69d9eba..efa4b18351 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/sweep_angle_gizmo.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/sweep_angle_gizmo.rs @@ -1,5 +1,6 @@ use crate::consts::{ARC_SNAP_THRESHOLD, GIZMO_HIDE_THRESHOLD}; use crate::messages::message::Message; +use crate::messages::portfolio::document::overlays::utility_functions::text_width; 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::network_interface::InputConnector; @@ -176,7 +177,11 @@ impl SweepAngleGizmo { .to_degrees(); let text = format!("{}°", format_rounded(display_angle, 2)); - let text_texture_width = overlay_context.get_width(&text) / 2.; + const FONT_SIZE: f64 = 12.; + + let text_width = text_width(&text, FONT_SIZE); + + let text_texture_width = text_width / 2.; let transform = calculate_arc_text_transform(angle, offset_angle, center, text_texture_width); diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 488542c6ee..12da555172 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -1,5 +1,6 @@ use crate::consts::{ANGLE_MEASURE_RADIUS_FACTOR, ARC_MEASURE_RADIUS_FACTOR_RANGE, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GRAY, SLOWING_DIVISOR}; use crate::messages::input_mapper::utility_types::input_mouse::{DocumentPosition, ViewportPosition}; +use crate::messages::portfolio::document::overlays::utility_functions::text_width; use crate::messages::portfolio::document::overlays::utility_types::{OverlayProvider, Pivot}; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::misc::PTZ; @@ -288,7 +289,9 @@ impl MessageHandler> for angle_in_degrees }; let text = format!("{}°", format_rounded(display_angle, 2)); - let text_texture_width = overlay_context.get_width(&text) / 2.; + const FONT_SIZE: f64 = 12.; + + let text_texture_width = text_width(&text, FONT_SIZE) / 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(