mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Migrate fonts to be stored as resources (#4165)
* Good start
* Impl
* allow responses.add(async { Message::NoOp });
* Fix
* Cache font blob in text node
* Refactor font handling to use Font struct direcly
* Fix fmt
* Embedd Font Resources by default
* Review
* Review
* Cache font info based on ResourceHash
This commit is contained in:
@@ -65,7 +65,6 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
||||
))),
|
||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::SubmitActiveGraphRender),
|
||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::SubmitEyedropperPreviewRender),
|
||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontDataLoad),
|
||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateUIScale),
|
||||
];
|
||||
/// Since we don't need to update the frontend multiple times per frame,
|
||||
@@ -80,6 +79,8 @@ const FRONTEND_UPDATE_MESSAGES: &[MessageDiscriminant] = &[
|
||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderScrollbars)),
|
||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerStructure),
|
||||
];
|
||||
// FrontendMessages that should be sent immediately
|
||||
const IMMEDIATE_FRONTEND_MESSAGES: &[FrontendMessageDiscriminant] = &[FrontendMessageDiscriminant::TriggerResolveResource, FrontendMessageDiscriminant::TriggerFontCatalogLoad];
|
||||
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[
|
||||
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(EventMessageDiscriminant::AnimationFrame)),
|
||||
MessageDiscriminant::Animation(AnimationMessageDiscriminant::IncrementFrameCounter),
|
||||
@@ -205,16 +206,13 @@ impl Dispatcher {
|
||||
self.message_handlers.dialog_message_handler.process_message(message, &mut queue, context);
|
||||
}
|
||||
Message::Frontend(message) => {
|
||||
// Handle these messages immediately by returning early
|
||||
if let FrontendMessage::TriggerFontDataLoad { .. } | FrontendMessage::TriggerFontCatalogLoad = message {
|
||||
self.responses.push(message);
|
||||
self.cleanup_queues(false);
|
||||
let decreminant = message.to_discriminant();
|
||||
self.responses.push(message);
|
||||
|
||||
// Return early to avoid running the code after the match block
|
||||
// Handle these message immediately by returning early
|
||||
if IMMEDIATE_FRONTEND_MESSAGES.contains(&decreminant) {
|
||||
self.cleanup_queues(false);
|
||||
return;
|
||||
} else {
|
||||
// `FrontendMessage`s are saved and will be sent to the frontend after the message queue is done being processed
|
||||
self.responses.push(message);
|
||||
}
|
||||
}
|
||||
Message::InputPreprocessor(message) => {
|
||||
@@ -325,7 +323,7 @@ impl Dispatcher {
|
||||
document_id,
|
||||
document,
|
||||
input: &self.message_handlers.input_preprocessor_message_handler,
|
||||
cached_data: &self.message_handlers.portfolio_message_handler.cached_data,
|
||||
fonts: &self.message_handlers.portfolio_message_handler.fonts,
|
||||
node_graph: &self.message_handlers.portfolio_message_handler.executor,
|
||||
preferences: &self.message_handlers.preferences_message_handler,
|
||||
viewport: &self.message_handlers.viewport_message_handler,
|
||||
|
||||
@@ -12,10 +12,10 @@ use crate::messages::portfolio::document::utility_types::wires::{WirePath, WireP
|
||||
use crate::messages::portfolio::utility_types::WorkspacePanelLayout;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::tool_messages::eyedropper_tool::PrimarySecondary;
|
||||
use graph_craft::application_io::resource::ResourceId;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::text::Font;
|
||||
use graphene_std::vector::style::FillChoiceUI;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -112,8 +112,11 @@ pub enum FrontendMessage {
|
||||
filename: String,
|
||||
},
|
||||
TriggerFontCatalogLoad,
|
||||
TriggerFontDataLoad {
|
||||
font: Font,
|
||||
TriggerResolveResource {
|
||||
#[serde(rename = "documentId")]
|
||||
document_id: DocumentId,
|
||||
#[serde(rename = "resourceId")]
|
||||
resource_id: ResourceId,
|
||||
url: String,
|
||||
},
|
||||
TriggerPersistenceReadState,
|
||||
|
||||
@@ -43,6 +43,21 @@ impl IntoFuture for MessageFuture {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MessageFuture> for Message {
|
||||
fn from(future: MessageFuture) -> Self {
|
||||
FutureMessage::Await { future }.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Message
|
||||
where
|
||||
T: Future<Output = Message> + Send + 'static,
|
||||
{
|
||||
fn from(future: T) -> Self {
|
||||
MessageFuture::new(future).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform-specific async-task executor.
|
||||
/// Runs `future`, sends the resolved message on `results`, then calls `wake`.
|
||||
pub trait MessageSpawner: Send + Sync {
|
||||
|
||||
@@ -21,7 +21,7 @@ use crate::messages::portfolio::document::properties_panel::properties_panel_mes
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, PTZ};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate, OutputConnector};
|
||||
use crate::messages::portfolio::utility_types::{CachedData, PanelType};
|
||||
use crate::messages::portfolio::utility_types::PanelType;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_blend_mode, get_fill, get_opacity};
|
||||
use crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys;
|
||||
@@ -52,7 +52,6 @@ use std::time::Duration;
|
||||
pub struct DocumentMessageContext<'a> {
|
||||
pub document_id: DocumentId,
|
||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||
pub cached_data: &'a CachedData,
|
||||
pub executor: &'a mut NodeGraphExecutor,
|
||||
pub current_tool: &'a ToolType,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
@@ -61,6 +60,7 @@ pub struct DocumentMessageContext<'a> {
|
||||
pub properties_panel_open: bool,
|
||||
pub viewport: &'a ViewportMessageHandler,
|
||||
pub resource_storage: &'a ResourceStorageMessageHandler,
|
||||
pub fonts: &'a FontsMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||
@@ -198,7 +198,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
let DocumentMessageContext {
|
||||
document_id,
|
||||
ipp,
|
||||
cached_data,
|
||||
executor,
|
||||
viewport,
|
||||
current_tool,
|
||||
@@ -207,6 +206,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
layers_panel_open,
|
||||
properties_panel_open,
|
||||
resource_storage,
|
||||
fonts,
|
||||
} = context;
|
||||
|
||||
match message {
|
||||
@@ -233,11 +233,12 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
}
|
||||
DocumentMessage::PropertiesPanel(message) => {
|
||||
let context = PropertiesPanelMessageContext {
|
||||
executor,
|
||||
network_interface: &mut self.network_interface,
|
||||
resources: &self.resources,
|
||||
selection_network_path: &self.selection_network_path,
|
||||
document_name: self.name.as_str(),
|
||||
executor,
|
||||
cached_data,
|
||||
fonts,
|
||||
properties_panel_open,
|
||||
};
|
||||
self.properties_panel_message_handler.process_message(message, responses, context);
|
||||
@@ -282,7 +283,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
graph_operation_message_handler.process_message(message, responses, context);
|
||||
}
|
||||
DocumentMessage::Resource(message) => {
|
||||
let context = ResourceMessageContext {};
|
||||
let context = ResourceMessageContext { document_id, fonts };
|
||||
self.resources.process_message(message, responses, context);
|
||||
}
|
||||
DocumentMessage::AlignSelectedLayers { axis, aggregate } => {
|
||||
@@ -933,21 +934,19 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
let mut document = self.clone();
|
||||
let resources_load_handle = resource_storage.resources();
|
||||
|
||||
responses.add(FutureMessage::Await {
|
||||
future: MessageFuture::new(async move {
|
||||
document.resources.garbage_collect(document.used_resources(false).as_ref());
|
||||
document.resources.embed_resources(resources_load_handle).await;
|
||||
responses.add(async move {
|
||||
document.resources.collect_garbage(document.used_resources(false).as_ref());
|
||||
document.resources.embed_resources(resources_load_handle).await;
|
||||
|
||||
let content = document.serialize_document().into_bytes().into();
|
||||
let content = document.serialize_document().into_bytes().into();
|
||||
|
||||
Message::Frontend(FrontendMessage::TriggerSaveDocument {
|
||||
document_id,
|
||||
name,
|
||||
path,
|
||||
folder,
|
||||
content,
|
||||
})
|
||||
}),
|
||||
Message::Frontend(FrontendMessage::TriggerSaveDocument {
|
||||
document_id,
|
||||
name,
|
||||
path,
|
||||
folder,
|
||||
content,
|
||||
})
|
||||
});
|
||||
}
|
||||
DocumentMessage::SavedDocument { path } => {
|
||||
@@ -2658,23 +2657,6 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads all of the fonts in the document.
|
||||
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) {
|
||||
let mut fonts_to_load = HashSet::new();
|
||||
|
||||
for (_, node, _) in self.document_network().recursive_nodes() {
|
||||
for input in &node.inputs {
|
||||
if let Some(TaggedValue::Font(font)) = input.as_value() {
|
||||
fonts_to_load.insert(font.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for font in fonts_to_load {
|
||||
responses.add(PortfolioMessage::LoadFontData { font });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>, animation_is_playing: bool, time: Duration) {
|
||||
let mut snapping_state = self.snapping_state.clone();
|
||||
let mut snapping_state2 = self.snapping_state.clone();
|
||||
@@ -3470,7 +3452,7 @@ impl DocumentMessageHandler {
|
||||
|
||||
pub fn garbage_collect_resources(&mut self) {
|
||||
let used_resources = self.used_resources(true);
|
||||
self.resources.garbage_collect(&used_resources);
|
||||
self.resources.collect_garbage(&used_resources);
|
||||
}
|
||||
|
||||
pub fn used_resources(&self, include_history: bool) -> Box<[ResourceId]> {
|
||||
|
||||
@@ -249,12 +249,13 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
}
|
||||
|
||||
pub fn insert_text(&mut self, text: String, font: Font, typesetting: TypesettingConfig, layer: LayerNodeIdentifier) {
|
||||
let font_resource_id = ResourceId::new();
|
||||
let text = resolve_proto_node_type(graphene_std::text::text::IDENTIFIER)
|
||||
.expect("Text node does not exist")
|
||||
.node_template_input_override([
|
||||
Some(NodeInput::scope("editor-api")),
|
||||
Some(NodeInput::value(TaggedValue::None, false)),
|
||||
Some(NodeInput::value(TaggedValue::String(text), false)),
|
||||
Some(NodeInput::value(TaggedValue::Font(font), false)),
|
||||
Some(NodeInput::value(TaggedValue::Resource(font_resource_id), false)),
|
||||
Some(NodeInput::value(TaggedValue::F64(typesetting.font_size), false)),
|
||||
Some(NodeInput::value(TaggedValue::F64(typesetting.line_height_ratio), false)),
|
||||
Some(NodeInput::value(TaggedValue::F64(typesetting.character_spacing), false)),
|
||||
@@ -264,6 +265,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
Some(NodeInput::value(TaggedValue::F64(typesetting.max_height.unwrap_or(100.)), false)),
|
||||
Some(NodeInput::value(TaggedValue::F64(typesetting.tilt), false)),
|
||||
Some(NodeInput::value(TaggedValue::TextAlign(typesetting.align), false)),
|
||||
Some(NodeInput::value(TaggedValue::Bool(false), false)),
|
||||
]);
|
||||
let transform = resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER)
|
||||
.expect("Transform node does not exist")
|
||||
@@ -276,6 +278,8 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
self.network_interface.insert_node(text_id, text, &[]);
|
||||
self.network_interface.move_node_to_chain_start(&text_id, layer, &[], self.import);
|
||||
|
||||
self.responses.add(DocumentMessage::Resource(ResourceMessage::AddFont { resource_id: font_resource_id, font }));
|
||||
|
||||
let transform_id = NodeId::new();
|
||||
self.network_interface.insert_node(transform_id, transform, &[]);
|
||||
self.network_interface.move_node_to_chain_start(&transform_id, layer, &[], self.import);
|
||||
|
||||
@@ -8,8 +8,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{
|
||||
DocumentNodeMetadata, DocumentNodePersistentMetadata, InputMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata,
|
||||
Vec2InputSettings, WidgetOverride,
|
||||
};
|
||||
use crate::messages::portfolio::utility_types::CachedData;
|
||||
use crate::messages::prelude::Message;
|
||||
use crate::messages::prelude::{FontsMessageHandler, Message, ResourceMessageHandler};
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
use glam::DVec2;
|
||||
use graph_craft::ProtoNodeIdentifier;
|
||||
@@ -28,10 +27,11 @@ use serde_json::Value;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
pub struct NodePropertiesContext<'a> {
|
||||
pub cached_data: &'a CachedData,
|
||||
pub responses: &'a mut VecDeque<Message>,
|
||||
pub executor: &'a mut NodeGraphExecutor,
|
||||
pub network_interface: &'a mut NodeNetworkInterface,
|
||||
pub resources: &'a ResourceMessageHandler,
|
||||
pub fonts: &'a FontsMessageHandler,
|
||||
pub selection_network_path: &'a [NodeId],
|
||||
pub document_name: &'a str,
|
||||
}
|
||||
|
||||
@@ -6,12 +6,13 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
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::network_interface::{InputConnector, NodeNetworkInterface};
|
||||
use crate::messages::portfolio::utility_types::{CachedData, FontCatalogStyle};
|
||||
use crate::messages::portfolio::fonts::utility_types::FontCatalogStyle;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use choice::enum_choice;
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::application_io::resource::ResourceId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
|
||||
use graph_craft::{Type, concrete};
|
||||
@@ -822,11 +823,27 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
|
||||
}
|
||||
|
||||
pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetInstance>, Option<Vec<WidgetInstance>>) {
|
||||
pub fn assign_font_message(node_id: NodeId, font: Font) -> Message {
|
||||
let resource_id = ResourceId::new();
|
||||
Message::Batched {
|
||||
messages: Box::new([
|
||||
DocumentMessage::Resource(ResourceMessage::AddFont { resource_id, font }).into(),
|
||||
NodeGraphMessage::SetInputValue {
|
||||
node_id,
|
||||
input_index: graphene_std::text::text::FontInput::INDEX,
|
||||
value: TaggedValue::Resource(resource_id),
|
||||
}
|
||||
.into(),
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
let ParameterWidgetsInfo {
|
||||
cached_data,
|
||||
document_node,
|
||||
node_id,
|
||||
index,
|
||||
resources,
|
||||
fonts,
|
||||
..
|
||||
} = parameter_widgets_info;
|
||||
|
||||
@@ -839,66 +856,32 @@ pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetI
|
||||
return (vec![], None);
|
||||
};
|
||||
|
||||
if let Some(TaggedValue::Font(font)) = &input.as_non_exposed_value() {
|
||||
if let Some(TaggedValue::Resource(resource_id)) = input.as_non_exposed_value() {
|
||||
let font = fonts.id_font(resources, *resource_id).unwrap_or_default();
|
||||
first_widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||
DropdownInput::new(vec![
|
||||
cached_data
|
||||
fonts
|
||||
.font_catalog
|
||||
.0
|
||||
.iter()
|
||||
.map(|family| {
|
||||
let FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(&font.font_style, "");
|
||||
let new_font = Font::new(family.name.clone(), family.closest_style(weight, italic).to_named_style());
|
||||
let commit_font = new_font.clone();
|
||||
MenuListEntry::new(family.name.clone())
|
||||
.label(family.name.clone())
|
||||
.font(family.closest_style(400, false).preview_url(&family.name))
|
||||
.on_update({
|
||||
// Construct the new font using the new family and the initial or previous style, although this style might not exist in the catalog
|
||||
let mut new_font = Font::new(family.name.clone(), font.font_style_to_restore.clone().unwrap_or_else(|| font.font_style.clone()));
|
||||
new_font.font_style_to_restore = font.font_style_to_restore.clone();
|
||||
|
||||
// If not already, store the initial style so it can be restored if the user switches to another family
|
||||
if new_font.font_style_to_restore.is_none() {
|
||||
new_font.font_style_to_restore = Some(new_font.font_style.clone());
|
||||
}
|
||||
|
||||
// Use the closest style available in the family for the new font to ensure the style exists
|
||||
let FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(&new_font.font_style, "");
|
||||
new_font.font_style = family.closest_style(weight, italic).to_named_style();
|
||||
|
||||
move |_| {
|
||||
let new_font = new_font.clone();
|
||||
|
||||
Message::Batched {
|
||||
messages: Box::new([
|
||||
PortfolioMessage::LoadFontData { font: new_font.clone() }.into(),
|
||||
update_value(move |_| TaggedValue::Font(new_font.clone()), node_id, index)(&()),
|
||||
]),
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_commit({
|
||||
// Use the new value from the user selection
|
||||
let font_family = family.name.clone();
|
||||
|
||||
// Use the previous style selection and extract its weight and italic properties, then find the closest style in the new family
|
||||
let FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(&font.font_style, "");
|
||||
let font_style = family.closest_style(weight, italic).to_named_style();
|
||||
|
||||
move |_| {
|
||||
// Intentionally drop `font_style_to_restore` on commit so the committed style becomes the new basis
|
||||
// for subsequent family switches. Preserving the original style intent is hover-only behavior.
|
||||
let new_font = Font::new(font_family.clone(), font_style.clone());
|
||||
|
||||
DeferMessage::AfterGraphRun {
|
||||
messages: vec![update_value(move |_| TaggedValue::Font(new_font.clone()), node_id, index)(&()), commit_value(&())],
|
||||
}
|
||||
.into()
|
||||
.on_update(move |_| assign_font_message(node_id, new_font.clone()))
|
||||
.on_commit(move |_| {
|
||||
DeferMessage::AfterGraphRun {
|
||||
messages: vec![assign_font_message(node_id, commit_font.clone()), commit_value(&())],
|
||||
}
|
||||
.into()
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
])
|
||||
.selected_index(cached_data.font_catalog.0.iter().position(|family| family.name == font.font_family).map(|i| i as u32))
|
||||
.selected_index(fonts.font_catalog.iter().position(|family| family.name == font.font_family).map(|i| i as u32))
|
||||
.virtual_scrolling(true)
|
||||
.widget_instance(),
|
||||
]);
|
||||
@@ -908,32 +891,18 @@ pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetI
|
||||
second_row.extend_from_slice(&[
|
||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||
DropdownInput::new({
|
||||
cached_data
|
||||
fonts
|
||||
.font_catalog
|
||||
.0
|
||||
.iter()
|
||||
.find(|family| family.name == font.font_family)
|
||||
.map(|family| {
|
||||
let build_entry = |style: &FontCatalogStyle| {
|
||||
let font_style = style.to_named_style();
|
||||
let font_family = font.font_family.clone();
|
||||
let new_font = Font::new(font_family, font_style.clone());
|
||||
MenuListEntry::new(font_style.clone())
|
||||
.label(font_style.clone())
|
||||
.on_update({
|
||||
let font_family = font.font_family.clone();
|
||||
let font_style = font_style.clone();
|
||||
|
||||
move |_| {
|
||||
// Keep the existing family
|
||||
let new_font = Font::new(font_family.clone(), font_style.clone());
|
||||
|
||||
Message::Batched {
|
||||
messages: Box::new([
|
||||
PortfolioMessage::LoadFontData { font: new_font.clone() }.into(),
|
||||
update_value(move |_| TaggedValue::Font(new_font.clone()), node_id, index)(&()),
|
||||
]),
|
||||
}
|
||||
}
|
||||
})
|
||||
.label(font_style)
|
||||
.on_update(move |_| assign_font_message(node_id, new_font.clone()))
|
||||
.on_commit(commit_value)
|
||||
};
|
||||
|
||||
@@ -946,9 +915,8 @@ pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetI
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.selected_index(
|
||||
cached_data
|
||||
fonts
|
||||
.font_catalog
|
||||
.0
|
||||
.iter()
|
||||
.find(|family| family.name == font.font_family)
|
||||
.and_then(|family| {
|
||||
@@ -2813,8 +2781,8 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
|
||||
}
|
||||
|
||||
pub struct ParameterWidgetsInfo<'a> {
|
||||
cached_data: &'a CachedData,
|
||||
network_interface: &'a NodeNetworkInterface,
|
||||
resources: &'a ResourceMessageHandler,
|
||||
selection_network_path: &'a [NodeId],
|
||||
document_node: Option<&'a DocumentNode>,
|
||||
node_id: NodeId,
|
||||
@@ -2824,6 +2792,7 @@ pub struct ParameterWidgetsInfo<'a> {
|
||||
input_type: FrontendGraphDataType,
|
||||
blank_assist: bool,
|
||||
exposable: bool,
|
||||
fonts: &'a FontsMessageHandler,
|
||||
}
|
||||
|
||||
impl<'a> ParameterWidgetsInfo<'a> {
|
||||
@@ -2836,9 +2805,10 @@ impl<'a> ParameterWidgetsInfo<'a> {
|
||||
let document_node = context.network_interface.document_node(&node_id, context.selection_network_path);
|
||||
|
||||
ParameterWidgetsInfo {
|
||||
cached_data: context.cached_data,
|
||||
network_interface: context.network_interface,
|
||||
resources: context.resources,
|
||||
selection_network_path: context.selection_network_path,
|
||||
fonts: context.fonts,
|
||||
document_node,
|
||||
node_id,
|
||||
index,
|
||||
|
||||
@@ -2,11 +2,12 @@ use super::utility_types::{DrawHandles, OverlayContext};
|
||||
use crate::consts::HIDE_HANDLE_DISTANCE;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::portfolio::fonts::FALLBACK_FONT_RESOURCE;
|
||||
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
|
||||
use crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::subpath::{Bezier, BezierHandles};
|
||||
use graphene_std::text::{Font, FontCache, TextAlign, TextContext, TypesettingConfig};
|
||||
use graphene_std::text::{TextAlign, TextContext, TypesettingConfig};
|
||||
use graphene_std::vector::misc::ManipulatorPointId;
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector};
|
||||
use std::collections::HashMap;
|
||||
@@ -221,16 +222,6 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
|
||||
}
|
||||
}
|
||||
|
||||
// Global lazy initialized font cache and text context
|
||||
pub static GLOBAL_FONT_CACHE: LazyLock<FontCache> = 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, FONT_DATA.to_vec());
|
||||
font_cache
|
||||
});
|
||||
|
||||
pub static GLOBAL_TEXT_CONTEXT: LazyLock<Mutex<TextContext>> = LazyLock::new(|| Mutex::new(TextContext::default()));
|
||||
|
||||
pub fn text_width(text: &str, font_size: f64) -> f64 {
|
||||
@@ -244,12 +235,8 @@ pub fn text_width(text: &str, font_size: f64) -> f64 {
|
||||
align: TextAlign::AlignLeft,
|
||||
};
|
||||
|
||||
// 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);
|
||||
let bounds = text_context.bounding_box(text, &FALLBACK_FONT_RESOURCE, typesetting, false);
|
||||
bounds.x
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ use crate::consts::{
|
||||
COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS,
|
||||
GRADIENT_MIDPOINT_DIAMOND_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, hex_to_rgba_u8};
|
||||
use crate::messages::portfolio::document::overlays::utility_functions::{GLOBAL_TEXT_CONTEXT, hex_to_rgba_u8};
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::fonts::FALLBACK_FONT_RESOURCE;
|
||||
use crate::messages::prelude::Message;
|
||||
use crate::messages::prelude::ViewportMessageHandler;
|
||||
use core::borrow::Borrow;
|
||||
@@ -14,7 +15,7 @@ use graphene_std::ATTR_TRANSFORM;
|
||||
use graphene_std::list::List;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::subpath::{self, Subpath};
|
||||
use graphene_std::text::{Font, TextAlign, TypesettingConfig};
|
||||
use graphene_std::text::{TextAlign, TypesettingConfig};
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::misc::point_to_dvec2;
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector};
|
||||
@@ -1115,21 +1116,16 @@ impl OverlayContextInternal {
|
||||
align: TextAlign::AlignLeft,
|
||||
};
|
||||
|
||||
// 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());
|
||||
|
||||
// Get text dimensions directly from layout
|
||||
let mut text_context = GLOBAL_TEXT_CONTEXT.lock().expect("Failed to lock global text context");
|
||||
let text_size = text_context.bounding_box(text, &font, &GLOBAL_FONT_CACHE, typesetting, false);
|
||||
let text_size = text_context.bounding_box(text, &FALLBACK_FONT_RESOURCE, typesetting, false);
|
||||
let text_width = text_size.x;
|
||||
let text_height = text_size.y;
|
||||
// Create a rect from the size (assuming text starts at origin)
|
||||
let text_bounds = kurbo::Rect::new(0., 0., text_width, text_height);
|
||||
|
||||
// Convert text to vector paths for rendering
|
||||
let text_list = text_context.to_path(text, &font, &GLOBAL_FONT_CACHE, typesetting, false);
|
||||
let text_list = text_context.to_path(text, &FALLBACK_FONT_RESOURCE, typesetting, false);
|
||||
|
||||
// Calculate position based on pivot
|
||||
let mut position = DVec2::ZERO;
|
||||
|
||||
@@ -3,17 +3,17 @@ use graphene_std::uuid::NodeId;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::portfolio::utility_types::CachedData;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct PropertiesPanelMessageContext<'a> {
|
||||
pub executor: &'a mut NodeGraphExecutor,
|
||||
pub network_interface: &'a mut NodeNetworkInterface,
|
||||
pub resources: &'a ResourceMessageHandler,
|
||||
pub selection_network_path: &'a [NodeId],
|
||||
pub document_name: &'a str,
|
||||
pub executor: &'a mut NodeGraphExecutor,
|
||||
pub cached_data: &'a CachedData,
|
||||
pub fonts: &'a FontsMessageHandler,
|
||||
pub properties_panel_open: bool,
|
||||
}
|
||||
|
||||
@@ -24,11 +24,12 @@ pub struct PropertiesPanelMessageHandler {}
|
||||
impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> for PropertiesPanelMessageHandler {
|
||||
fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque<Message>, context: PropertiesPanelMessageContext) {
|
||||
let PropertiesPanelMessageContext {
|
||||
executor,
|
||||
network_interface,
|
||||
resources,
|
||||
selection_network_path,
|
||||
document_name,
|
||||
executor,
|
||||
cached_data,
|
||||
fonts,
|
||||
properties_panel_open,
|
||||
} = context;
|
||||
|
||||
@@ -46,12 +47,13 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
|
||||
}
|
||||
|
||||
let mut node_properties_context = NodePropertiesContext {
|
||||
cached_data,
|
||||
responses,
|
||||
executor,
|
||||
network_interface,
|
||||
resources,
|
||||
selection_network_path,
|
||||
document_name,
|
||||
executor,
|
||||
fonts,
|
||||
};
|
||||
let layout = Layout(NodeGraphMessageHandler::collate_properties(&mut node_properties_context));
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
use crate::messages::prelude::*;
|
||||
use graph_craft::application_io::resource::ResourceId;
|
||||
use graphene_std::text::Font;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[impl_message(Message, DocumentMessage, Resource)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum ResourceMessage {
|
||||
StoreEmbedded { resource_id: ResourceId, data: Arc<[u8]> },
|
||||
AddFont { resource_id: ResourceId, font: Font },
|
||||
Resolve,
|
||||
ResolveStep { resource_id: ResourceId },
|
||||
Resolved { resource_id: ResourceId, data: Arc<[u8]> },
|
||||
}
|
||||
|
||||
@@ -3,25 +3,149 @@ use crate::messages::prelude::*;
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD as BASE64;
|
||||
use graph_craft::application_io::resource::{DataSource, LoadResource, Resource, ResourceHash, ResourceId, ResourceRegistry};
|
||||
use graphene_std::text::Font;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct ResourceMessageContext {}
|
||||
pub struct ResourceMessageContext<'a> {
|
||||
pub document_id: DocumentId,
|
||||
pub fonts: &'a FontsMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, ExtractField)]
|
||||
pub struct ResourceMessageHandler {
|
||||
pub registry: ResourceRegistry,
|
||||
pub embedded: EmbeddedResources,
|
||||
#[serde(skip)]
|
||||
pending_resolves: HashMap<ResourceId, Option<ResolveProgress>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
struct ResolveProgress {
|
||||
index: usize,
|
||||
source: DataSource,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<ResourceMessage, ResourceMessageContext> for ResourceMessageHandler {
|
||||
fn process_message(&mut self, message: ResourceMessage, responses: &mut VecDeque<Message>, _context: ResourceMessageContext) {
|
||||
impl MessageHandler<ResourceMessage, ResourceMessageContext<'_>> for ResourceMessageHandler {
|
||||
fn process_message(&mut self, message: ResourceMessage, responses: &mut VecDeque<Message>, context: ResourceMessageContext) {
|
||||
let ResourceMessageContext { document_id, fonts } = context;
|
||||
|
||||
match message {
|
||||
ResourceMessage::StoreEmbedded { resource_id, data } => {
|
||||
let hash = ResourceHash::from(data.as_ref());
|
||||
self.registry.push_source_back(&resource_id, DataSource::Embedded);
|
||||
self.registry.resolve(&resource_id, hash);
|
||||
responses.add(ResourceStorageMessage::Store { data });
|
||||
responses.add(ResourceMessage::Resolve);
|
||||
}
|
||||
ResourceMessage::AddFont { resource_id, font } => {
|
||||
let style = fonts.font_catalog.find_font_style_in_catalog(&font);
|
||||
let style_name = style.map(|style| style.to_named_style()).unwrap_or_else(|| font.font_style.clone());
|
||||
self.registry.push_source_back(&resource_id, DataSource::Embedded);
|
||||
self.registry.push_source_back(
|
||||
&resource_id,
|
||||
DataSource::Font {
|
||||
family: font.font_family,
|
||||
style: Some(style_name),
|
||||
},
|
||||
);
|
||||
responses.add(ResourceMessage::Resolve);
|
||||
}
|
||||
ResourceMessage::Resolve => {
|
||||
let unresolved_ids: Vec<ResourceId> = self.registry.unresolved().map(|info| info.id).collect();
|
||||
for id in unresolved_ids {
|
||||
if self.pending_resolves.contains_key(&id) {
|
||||
continue;
|
||||
}
|
||||
self.pending_resolves.insert(id, None);
|
||||
responses.add(ResourceMessage::ResolveStep { resource_id: id });
|
||||
}
|
||||
}
|
||||
ResourceMessage::ResolveStep { resource_id } => {
|
||||
let Some(progress) = self.pending_resolves.get_mut(&resource_id) else { return };
|
||||
|
||||
let Some(info) = self.registry.info(&resource_id) else {
|
||||
log::error!("ResolveStep for {resource_id}: no registry entry");
|
||||
self.pending_resolves.remove(&resource_id);
|
||||
return;
|
||||
};
|
||||
|
||||
let index = if let Some(progress) = progress { progress.index + 1 } else { 0 };
|
||||
let Some(source) = info.sources.get(index).cloned() else {
|
||||
log::error!("ResolveStep for {resource_id}: no more sources to try");
|
||||
self.pending_resolves.remove(&resource_id);
|
||||
return;
|
||||
};
|
||||
*progress = Some(ResolveProgress { index, source: source.clone() });
|
||||
|
||||
match source {
|
||||
DataSource::Embedded => {
|
||||
// Embedded resources are loaded on document load.
|
||||
// If we get to this point, it means the resource was not embedded and we should try the next source.
|
||||
responses.add(ResourceMessage::ResolveStep { resource_id });
|
||||
}
|
||||
DataSource::Url(url) => {
|
||||
responses.add(FrontendMessage::TriggerResolveResource {
|
||||
document_id,
|
||||
resource_id,
|
||||
url: url.to_string(),
|
||||
});
|
||||
}
|
||||
DataSource::Font { family, style } => {
|
||||
let font = match style {
|
||||
Some(style) => Font::new(family, style),
|
||||
None => Font::new_with_default_style(family),
|
||||
};
|
||||
if let Some(hash) = fonts.cached_hash(&font) {
|
||||
self.registry.resolve(&resource_id, hash);
|
||||
self.pending_resolves.remove(&resource_id);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
return;
|
||||
}
|
||||
if let Some(url) = fonts.cached_url(&font) {
|
||||
responses.add(FrontendMessage::TriggerResolveResource { document_id, resource_id, url });
|
||||
return;
|
||||
}
|
||||
responses.add(FrontendMessage::TriggerFontCatalogLoad);
|
||||
self.pending_resolves.remove(&resource_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
ResourceMessage::Resolved { resource_id, data } => {
|
||||
let hash = ResourceHash::from(data.as_ref());
|
||||
let Some(progress) = self.pending_resolves.remove(&resource_id).and_then(|p| p) else {
|
||||
log::error!("Resolved message for {resource_id} with no pending resolve");
|
||||
return;
|
||||
};
|
||||
let Some(info) = self.registry.info(&resource_id) else {
|
||||
// ResourceId was removed from registry after resolve started.
|
||||
// This can happen if the document was modified while resolves were in-flight.
|
||||
// Likely safe to ignore for now.
|
||||
// TODO: Consider adding cleaner cancelation for in-flight resolves.
|
||||
return;
|
||||
};
|
||||
let Some(source) = info.sources.get(progress.index).cloned() else {
|
||||
log::error!("Resolved message for {resource_id} with no current source");
|
||||
return;
|
||||
};
|
||||
if progress.source != source {
|
||||
log::error!("Resolved message for {resource_id} with mismatched source");
|
||||
return;
|
||||
}
|
||||
|
||||
self.registry.resolve(&resource_id, hash);
|
||||
responses.add(ResourceStorageMessage::Store { data });
|
||||
|
||||
if let DataSource::Font { family, style } = source {
|
||||
let font = match style {
|
||||
Some(style) => Font::new(family, style),
|
||||
None => Font::new_with_default_style(family),
|
||||
};
|
||||
responses.add(FontsMessage::ResourceResolved { font, hash });
|
||||
}
|
||||
|
||||
responses.add(ResourceMessage::Resolve);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,7 +178,7 @@ impl ResourceMessageHandler {
|
||||
self.embedded = EmbeddedResources::from_iter(futures::future::join_all(embedded).await.into_iter().flatten());
|
||||
}
|
||||
|
||||
pub fn garbage_collect(&mut self, used: &[ResourceId]) {
|
||||
pub fn collect_garbage(&mut self, used: &[ResourceId]) {
|
||||
let used = HashSet::<ResourceId>::from_iter(used.iter().cloned());
|
||||
let unused = self.registry.ids().filter(|id| !used.contains(id)).collect::<Vec<_>>();
|
||||
unused.into_iter().for_each(|id| {
|
||||
|
||||
@@ -1685,6 +1685,28 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
}
|
||||
}
|
||||
|
||||
// Convert text nodes from the old `editor-api` scope + `Font` input to a single font `Resource` input.
|
||||
// The chosen typeface is recorded as a `DataSource::Font` in the document's resource registry and loaded on open.
|
||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER) && inputs_count == 13 && matches!(node.inputs.first(), Some(NodeInput::Scope(_))) {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path);
|
||||
|
||||
if let Some(TaggedValue::Font(font)) = node.inputs.get(2).and_then(|input| input.as_value()) {
|
||||
let resource_id = ResourceId::new();
|
||||
document.resources.registry.push_source_back(
|
||||
&resource_id,
|
||||
DataSource::Font {
|
||||
family: font.font_family.clone(),
|
||||
style: Some(font.font_style.clone()),
|
||||
},
|
||||
);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Resource(resource_id), false), network_path);
|
||||
}
|
||||
}
|
||||
|
||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::raster_nodes::std_nodes::noise_pattern::IDENTIFIER) && inputs_count == 15 {
|
||||
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
5
editor/src/messages/portfolio/fonts/fallback.rs
Normal file
5
editor/src/messages/portfolio/fonts/fallback.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use graph_craft::application_io::resource::Resource;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
const FALLBACK_FONT_BYTES: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
|
||||
pub static FALLBACK_FONT_RESOURCE: LazyLock<Resource> = LazyLock::new(|| Resource::new(FALLBACK_FONT_BYTES));
|
||||
25
editor/src/messages/portfolio/fonts/fonts_message.rs
Normal file
25
editor/src/messages/portfolio/fonts/fonts_message.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use crate::messages::portfolio::fonts::utility_types::FontCatalog;
|
||||
use crate::messages::prelude::*;
|
||||
use graph_craft::application_io::resource::{Resource, ResourceHash};
|
||||
use graphene_std::text::Font;
|
||||
|
||||
#[impl_message(Message, PortfolioMessage, Fonts)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum FontsMessage {
|
||||
CatalogLoaded {
|
||||
catalog: FontCatalog,
|
||||
},
|
||||
ResourceResolved {
|
||||
font: Font,
|
||||
hash: ResourceHash,
|
||||
},
|
||||
Load {
|
||||
font: Font,
|
||||
response: Box<Message>,
|
||||
},
|
||||
Cached {
|
||||
hash: ResourceHash,
|
||||
#[serde(skip, default = "Resource::empty")]
|
||||
resource: Resource,
|
||||
},
|
||||
}
|
||||
112
editor/src/messages/portfolio/fonts/fonts_message_handler.rs
Normal file
112
editor/src/messages/portfolio/fonts/fonts_message_handler.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
use crate::messages::portfolio::fonts::FALLBACK_FONT_RESOURCE;
|
||||
use crate::messages::portfolio::fonts::utility_types::FontCatalog;
|
||||
use crate::messages::prelude::*;
|
||||
use graph_craft::application_io::resource::{DataSource, Resource, ResourceHash, ResourceId};
|
||||
use graphene_std::text::Font;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct FontsMessageContext<'a> {
|
||||
pub resource_storage: &'a ResourceStorageMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct FontsMessageHandler {
|
||||
pub font_catalog: FontCatalog,
|
||||
font_hashes: HashMap<Font, ResourceHash>,
|
||||
font_data: HashMap<ResourceHash, Resource>,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<FontsMessage, FontsMessageContext<'_>> for FontsMessageHandler {
|
||||
fn process_message(&mut self, message: FontsMessage, responses: &mut VecDeque<Message>, context: FontsMessageContext) {
|
||||
let FontsMessageContext { resource_storage } = context;
|
||||
|
||||
match message {
|
||||
FontsMessage::CatalogLoaded { catalog } => {
|
||||
self.font_catalog = catalog;
|
||||
responses.add(PortfolioMessage::ResolveResources);
|
||||
}
|
||||
FontsMessage::ResourceResolved { font, hash } => {
|
||||
let font = self.normalize(font);
|
||||
self.font_hashes.insert(font, hash);
|
||||
}
|
||||
FontsMessage::Load { font, response } => {
|
||||
let font = self.normalize(font);
|
||||
let Some(hash) = self.font_hashes.get(&font).copied() else {
|
||||
log::warn!("FontsMessage::Load for {font:?} with no known hash; ignoring");
|
||||
return;
|
||||
};
|
||||
if self.font_data.contains_key(&hash) {
|
||||
responses.add(*response);
|
||||
return;
|
||||
}
|
||||
let loader = resource_storage.resources();
|
||||
responses.add(async move {
|
||||
let resource = loader.load(hash).await;
|
||||
match resource {
|
||||
Some(resource) => Message::Batched {
|
||||
messages: Box::new([FontsMessage::Cached { hash, resource }.into(), *response]),
|
||||
},
|
||||
None => {
|
||||
log::warn!("Storage missing data for font hash {hash}");
|
||||
*response
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
FontsMessage::Cached { hash, resource } => {
|
||||
self.font_data.insert(hash, resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
advertise_actions!(FontsMessageDiscriminant;);
|
||||
}
|
||||
|
||||
impl FontsMessageHandler {
|
||||
pub fn cached_hash(&self, font: &Font) -> Option<ResourceHash> {
|
||||
let font = self.normalize(font.clone());
|
||||
self.font_hashes.get(&font).copied()
|
||||
}
|
||||
|
||||
pub fn cached_url(&self, font: &Font) -> Option<String> {
|
||||
let font = self.normalize(font.clone());
|
||||
self.font_catalog.download_url(&font)
|
||||
}
|
||||
|
||||
pub fn get_resource_or_queue_load(&self, font: &Font, responses: &mut VecDeque<Message>) -> Resource {
|
||||
let font = self.normalize(font.clone());
|
||||
if let Some(hash) = self.font_hashes.get(&font) {
|
||||
if let Some(resource) = self.font_data.get(hash) {
|
||||
return resource.clone();
|
||||
}
|
||||
responses.add(FontsMessage::Load {
|
||||
font: font.clone(),
|
||||
response: Message::NoOp.into(),
|
||||
});
|
||||
}
|
||||
FALLBACK_FONT_RESOURCE.clone()
|
||||
}
|
||||
|
||||
pub fn id_font(&self, resources: &ResourceMessageHandler, resource_id: ResourceId) -> Option<Font> {
|
||||
let info = resources.registry.info(&resource_id)?;
|
||||
info.sources.iter().find_map(|source| match source {
|
||||
DataSource::Font { family, style } => Some(match style {
|
||||
Some(style) => Font::new(family.clone(), style.clone()),
|
||||
None => Font::new_with_default_style(family.clone()),
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn used_resources(&self) -> impl Iterator<Item = ResourceHash> + '_ {
|
||||
self.font_hashes.values().copied().chain(self.font_data.keys().copied())
|
||||
}
|
||||
|
||||
fn normalize(&self, font: Font) -> Font {
|
||||
match self.font_catalog.find_font_style_in_catalog(&font) {
|
||||
Some(style) => Font::new(font.font_family, style.to_named_style()),
|
||||
None => font,
|
||||
}
|
||||
}
|
||||
}
|
||||
12
editor/src/messages/portfolio/fonts/mod.rs
Normal file
12
editor/src/messages/portfolio/fonts/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
mod fallback;
|
||||
mod fonts_message;
|
||||
mod fonts_message_handler;
|
||||
|
||||
pub mod utility_types;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use fallback::FALLBACK_FONT_RESOURCE;
|
||||
#[doc(inline)]
|
||||
pub use fonts_message::{FontsMessage, FontsMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use fonts_message_handler::{FontsMessageContext, FontsMessageHandler};
|
||||
104
editor/src/messages/portfolio/fonts/utility_types.rs
Normal file
104
editor/src/messages/portfolio/fonts/utility_types.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use graphene_std::text::Font;
|
||||
|
||||
// TODO: Should this be a BTreeMap instead?
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FontCatalog(Vec<FontCatalogFamily>);
|
||||
|
||||
impl FontCatalog {
|
||||
pub fn find_font_style_in_catalog(&self, font: &Font) -> Option<FontCatalogStyle> {
|
||||
let family = self.0.iter().find(|family| family.name == font.font_family);
|
||||
|
||||
let found_style = family.map(|family| {
|
||||
let FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(&font.font_style, "");
|
||||
family.closest_style(weight, italic).clone()
|
||||
});
|
||||
|
||||
if found_style.is_none() {
|
||||
log::warn!("Font not found in catalog: {:?}", font);
|
||||
}
|
||||
|
||||
found_style
|
||||
}
|
||||
|
||||
pub fn download_url(&self, font: &Font) -> Option<String> {
|
||||
let catalog_family = self.0.iter().find(|catalog_family| catalog_family.name == font.font_family)?;
|
||||
let FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(&font.font_style, "");
|
||||
Some(catalog_family.closest_style(weight, italic).url.clone())
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &FontCatalogFamily> {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<FontCatalogFamily>> for FontCatalog {
|
||||
fn from(value: Vec<FontCatalogFamily>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(from_wasm_abi))]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FontCatalogFamily {
|
||||
/// The font family name.
|
||||
pub name: String,
|
||||
/// The font styles (variants) available for the font family.
|
||||
pub styles: Vec<FontCatalogStyle>,
|
||||
}
|
||||
|
||||
impl FontCatalogFamily {
|
||||
/// Finds the closest style to the given weight and italic setting.
|
||||
/// Aims to find the nearest weight while maintaining the italic setting if possible, but italic may change if no other option is available.
|
||||
pub fn closest_style(&self, weight: u32, italic: bool) -> &FontCatalogStyle {
|
||||
static FALLBACK_STYLE: FontCatalogStyle = FontCatalogStyle {
|
||||
weight: 400,
|
||||
italic: false,
|
||||
url: String::new(),
|
||||
};
|
||||
|
||||
self.styles
|
||||
.iter()
|
||||
.map(|style| ((style.weight as i32 - weight as i32).unsigned_abs() + 10000 * (style.italic != italic) as u32, style))
|
||||
.min_by_key(|(distance, _)| *distance)
|
||||
.map(|(_, style)| style)
|
||||
.unwrap_or(self.styles.first().unwrap_or(&FALLBACK_STYLE))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FontCatalogStyle {
|
||||
pub weight: u32,
|
||||
pub italic: bool,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
impl FontCatalogStyle {
|
||||
pub fn to_named_style(&self) -> String {
|
||||
let weight = self.weight;
|
||||
let italic = self.italic;
|
||||
|
||||
let named_weight = Font::named_weight(weight);
|
||||
let maybe_italic = if italic { " Italic" } else { "" };
|
||||
|
||||
format!("{named_weight}{maybe_italic} ({weight})")
|
||||
}
|
||||
|
||||
pub fn from_named_style(named_style: &str, url: impl Into<String>) -> FontCatalogStyle {
|
||||
let weight = named_style.split_terminator(['(', ')']).next_back().and_then(|x| x.parse::<u32>().ok()).unwrap_or(400);
|
||||
let italic = named_style.contains("Italic (");
|
||||
FontCatalogStyle { weight, italic, url: url.into() }
|
||||
}
|
||||
|
||||
/// Get the URL for the stylesheet for loading a font preview for this style of the given family name, subsetted to only the letters in the family name.
|
||||
pub fn preview_url(&self, family: impl Into<String>) -> String {
|
||||
let name = family.into().replace(' ', "+");
|
||||
let italic = if self.italic { "ital," } else { "" };
|
||||
let weight = self.weight;
|
||||
format!("https://fonts.googleapis.com/css2?display=swap&family={name}:{italic}wght@{weight}&text={name}")
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,12 @@ mod portfolio_message_handler;
|
||||
|
||||
pub mod document;
|
||||
pub mod document_migration;
|
||||
pub mod fonts;
|
||||
pub mod persistent_state;
|
||||
pub mod utility_types;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use fonts::{FontsMessage, FontsMessageContext, FontsMessageHandler};
|
||||
#[doc(inline)]
|
||||
pub use persistent_state::{PersistentStateMessage, PersistentStateMessageContext, PersistentStateMessageHandler};
|
||||
#[doc(inline)]
|
||||
|
||||
@@ -52,3 +52,9 @@ impl MessageHandler<PersistentStateMessage, PersistentStateMessageContext> for P
|
||||
|
||||
advertise_actions!(PersistentStateMessageDiscriminant;);
|
||||
}
|
||||
|
||||
impl PersistentStateMessageHandler {
|
||||
pub fn loaded(&self) -> bool {
|
||||
self.loaded
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,9 @@ use super::persistent_state::PersistentStateMessage;
|
||||
use super::utility_types::{DockingSplitDirection, PanelGroupId, PanelType};
|
||||
use crate::messages::frontend::utility_types::{ExportBounds, FileType, PersistedState};
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||
use crate::messages::portfolio::utility_types::FontCatalog;
|
||||
use crate::messages::prelude::*;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::text::Font;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[impl_message(Message, Portfolio)]
|
||||
@@ -17,6 +15,8 @@ pub enum PortfolioMessage {
|
||||
#[child]
|
||||
Document(DocumentMessage),
|
||||
#[child]
|
||||
Fonts(FontsMessage),
|
||||
#[child]
|
||||
PersistentState(PersistentStateMessage),
|
||||
|
||||
// Messages
|
||||
@@ -51,20 +51,10 @@ pub enum PortfolioMessage {
|
||||
DestroyAllDocuments,
|
||||
EditorPreferences,
|
||||
GarbageCollectResources,
|
||||
FontCatalogLoaded {
|
||||
catalog: FontCatalog,
|
||||
},
|
||||
LoadFontData {
|
||||
font: Font,
|
||||
},
|
||||
FontLoaded {
|
||||
font_family: String,
|
||||
font_style: String,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
LoadDocumentResources {
|
||||
ResolveDocumentResources {
|
||||
document_id: DocumentId,
|
||||
},
|
||||
ResolveResources,
|
||||
LoadPersistedState {
|
||||
state: PersistedState,
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use super::document::utility_types::network_interface;
|
||||
use super::persistent_state::{PersistentStateMessage, PersistentStateMessageContext, PersistentStateMessageHandler};
|
||||
use super::utility_types::{CachedData, PanelLayoutSubdivision, PanelType, WorkspacePanelLayout};
|
||||
use super::utility_types::{PanelLayoutSubdivision, PanelType, WorkspacePanelLayout};
|
||||
use crate::application::{Editor, generate_uuid};
|
||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH, FILE_EXTENSION};
|
||||
use crate::messages::animation::TimingInformation;
|
||||
@@ -32,7 +32,6 @@ use graphene_std::Color;
|
||||
use graphene_std::raster_types::Image;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::subpath::BezierHandles;
|
||||
use graphene_std::text::Font;
|
||||
use graphene_std::vector::misc::HandleId;
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector, VectorModificationType};
|
||||
use std::path::PathBuf;
|
||||
@@ -68,7 +67,7 @@ pub struct PortfolioMessageHandler {
|
||||
document_ids: VecDeque<DocumentId>,
|
||||
pub(crate) active_document_id: Option<DocumentId>,
|
||||
persistent_state: PersistentStateMessageHandler,
|
||||
pub cached_data: CachedData,
|
||||
pub fonts: FontsMessageHandler,
|
||||
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
|
||||
pub executor: NodeGraphExecutor,
|
||||
pub selection_mode: SelectionMode,
|
||||
@@ -99,7 +98,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
let document_inputs = DocumentMessageContext {
|
||||
document_id,
|
||||
ipp,
|
||||
cached_data: &self.cached_data,
|
||||
fonts: &self.fonts,
|
||||
executor: &mut self.executor,
|
||||
current_tool,
|
||||
preferences,
|
||||
@@ -118,6 +117,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
};
|
||||
self.persistent_state.process_message(message, responses, context);
|
||||
}
|
||||
PortfolioMessage::Fonts(message) => {
|
||||
let context = FontsMessageContext { resource_storage };
|
||||
self.fonts.process_message(message, responses, context);
|
||||
}
|
||||
|
||||
// Messages
|
||||
PortfolioMessage::Init => {
|
||||
@@ -169,7 +172,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
let document_inputs = DocumentMessageContext {
|
||||
document_id,
|
||||
ipp,
|
||||
cached_data: &self.cached_data,
|
||||
fonts: &self.fonts,
|
||||
executor: &mut self.executor,
|
||||
current_tool,
|
||||
preferences,
|
||||
@@ -208,6 +211,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
document_id,
|
||||
document: document.serialize_document(),
|
||||
});
|
||||
responses.add(PersistentStateMessage::WriteState);
|
||||
}
|
||||
PortfolioMessage::CloseActiveDocumentWithConfirmation => {
|
||||
if let Some(document_id) = self.active_document_id {
|
||||
@@ -386,78 +390,16 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
responses.add(PersistentStateMessage::WriteState);
|
||||
}
|
||||
PortfolioMessage::FontCatalogLoaded { catalog } => {
|
||||
self.cached_data.font_catalog = catalog;
|
||||
|
||||
if let Some(document_id) = self.active_document_id {
|
||||
responses.add(PortfolioMessage::LoadDocumentResources { document_id });
|
||||
}
|
||||
|
||||
// Load the default font
|
||||
let font = Font::new(graphene_std::consts::DEFAULT_FONT_FAMILY.into(), graphene_std::consts::DEFAULT_FONT_STYLE.into());
|
||||
responses.add(PortfolioMessage::LoadFontData { font });
|
||||
}
|
||||
PortfolioMessage::LoadFontData { font } => {
|
||||
if let Some(style) = self.cached_data.font_catalog.find_font_style_in_catalog(&font) {
|
||||
let font = Font::new(font.font_family, style.to_named_style());
|
||||
|
||||
if !self.cached_data.font_cache.loaded_font(&font) {
|
||||
responses.add(FrontendMessage::TriggerFontDataLoad { font, url: style.url });
|
||||
}
|
||||
}
|
||||
}
|
||||
PortfolioMessage::FontLoaded { font_family, font_style, data } => {
|
||||
let font = Font::new(font_family, font_style);
|
||||
self.cached_data.font_cache.insert(font, data);
|
||||
self.executor.update_font_cache(self.cached_data.font_cache.clone());
|
||||
|
||||
for document_id in self.document_ids.iter() {
|
||||
let node_to_inspect = self.node_to_inspect();
|
||||
|
||||
let Some(document) = self.documents.get_mut(document_id) else {
|
||||
if self.unloaded_documents.contains_key(document_id) {
|
||||
continue;
|
||||
}
|
||||
log::error!("Tried to render non-existent document");
|
||||
continue;
|
||||
};
|
||||
|
||||
let document_to_viewport = document
|
||||
.navigation_handler
|
||||
.calculate_offset_transform(viewport.center_in_viewport_space().into(), &document.document_ptz);
|
||||
let pointer_position = document_to_viewport.inverse().transform_point2(ipp.mouse.position);
|
||||
|
||||
let scale = viewport.scale();
|
||||
// Use exact physical dimensions from browser (via ResizeObserver's devicePixelContentBoxSize)
|
||||
let physical_resolution = viewport.size().to_physical().into_dvec2().round().as_uvec2();
|
||||
|
||||
// TODO: Remove this when we do the SVG rendering with a separate library on desktop, thus avoiding a need for the hole punch.
|
||||
// TODO: See #3796. There is a second instance of this todo comment and code block (be sure to remove both).
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
responses.add_front(FrontendMessage::UpdateViewportHolePunch {
|
||||
active: document.render_mode != graphene_std::vector::style::RenderMode::SvgPreview,
|
||||
});
|
||||
|
||||
if let Ok(message) = self
|
||||
.executor
|
||||
.submit_node_graph_evaluation(document, *document_id, physical_resolution, scale, timing_information, node_to_inspect, true, pointer_position)
|
||||
{
|
||||
responses.add_front(message);
|
||||
}
|
||||
}
|
||||
|
||||
if self.active_document_mut().is_some() {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
if current_tool == &ToolType::Text {
|
||||
responses.add(TextToolMessage::RefreshEditingFontData);
|
||||
}
|
||||
}
|
||||
PortfolioMessage::EditorPreferences => self.executor.update_editor_preferences(preferences.editor_preferences()),
|
||||
PortfolioMessage::GarbageCollectResources => {
|
||||
if !self.persistent_state.loaded() {
|
||||
// We don't know what can be safely garbage collected
|
||||
return;
|
||||
}
|
||||
|
||||
let mut used_resources = HashSet::new();
|
||||
for (id, info) in self.unloaded_documents.iter() {
|
||||
log::info!("Checking resources for unloaded document {:?}: {:?}", info.name, info.resources);
|
||||
if let Some(resources) = &info.resources {
|
||||
used_resources.extend(resources.iter());
|
||||
} else {
|
||||
@@ -469,21 +411,26 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
document.garbage_collect_resources();
|
||||
used_resources.extend(document.resources.registry.resolved().filter_map(|info| info.hash.cloned()));
|
||||
}
|
||||
used_resources.extend(self.fonts.used_resources());
|
||||
responses.add(ResourceStorageMessage::GarbageCollect {
|
||||
used: Vec::from_iter(used_resources).into_boxed_slice(),
|
||||
});
|
||||
}
|
||||
PortfolioMessage::LoadDocumentResources { document_id } => {
|
||||
let catalog = &self.cached_data.font_catalog;
|
||||
|
||||
if catalog.0.is_empty() {
|
||||
PortfolioMessage::ResolveResources => {
|
||||
for document_id in self.document_ids.iter().copied().collect::<Vec<_>>() {
|
||||
responses.add(PortfolioMessage::ResolveDocumentResources { document_id });
|
||||
}
|
||||
}
|
||||
PortfolioMessage::ResolveDocumentResources { document_id } => {
|
||||
if self.fonts.font_catalog.is_empty() {
|
||||
responses.add_front(FrontendMessage::TriggerFontCatalogLoad);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(document) = self.documents.get_mut(&document_id) {
|
||||
document.load_layer_resources(responses);
|
||||
}
|
||||
responses.add(PortfolioMessage::DocumentPassMessage {
|
||||
document_id,
|
||||
message: DocumentMessage::Resource(ResourceMessage::Resolve),
|
||||
});
|
||||
}
|
||||
PortfolioMessage::LoadPersistedState { state } => {
|
||||
if let Some(layout) = state.workspace_layout {
|
||||
@@ -884,6 +831,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
let mut document = match document {
|
||||
Ok(document) => document,
|
||||
Err(e) => {
|
||||
log::error!("{e}");
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
// TODO: (Only the `if` branch, the `else` branch's manual-open dialog stays)
|
||||
if document_is_auto_saved {
|
||||
@@ -908,7 +856,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||
self.tick_autosave_load_progress(responses, true);
|
||||
} else {
|
||||
log::error!("{e}");
|
||||
let name = document_name
|
||||
.filter(|n| !n.trim().is_empty())
|
||||
.or_else(|| document_path.as_ref().and_then(|p| p.file_stem()).map(|s| s.to_string_lossy().into_owned()))
|
||||
@@ -1134,7 +1081,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
added_nodes = true;
|
||||
}
|
||||
|
||||
document.load_layer_resources(responses);
|
||||
let new_ids: HashMap<_, _> = entry.nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect();
|
||||
let layer = LayerNodeIdentifier::new_unchecked(new_ids[&NodeId(0)]);
|
||||
all_new_ids.extend(new_ids.values().cloned());
|
||||
@@ -1619,7 +1565,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
};
|
||||
if !document.is_loaded {
|
||||
document.is_loaded = true;
|
||||
responses.add(PortfolioMessage::LoadDocumentResources { document_id });
|
||||
responses.add(PortfolioMessage::ResolveDocumentResources { document_id });
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
responses.add(PropertiesPanelMessage::Clear);
|
||||
}
|
||||
@@ -1665,6 +1611,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
return;
|
||||
};
|
||||
|
||||
// Skip rendering while any resource is still unresolved — the preprocessor would otherwise fail with
|
||||
// `ResourceNotFound`. `ResourceMessage::Resolved` queues `RunDocumentGraph` once each id resolves,
|
||||
// so the render fires automatically once the registry is complete.
|
||||
if document.resources.registry.unresolved().next().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let document_to_viewport = document
|
||||
.navigation_handler
|
||||
.calculate_offset_transform(viewport.center_in_viewport_space().into(), &document.document_ptz);
|
||||
|
||||
@@ -1,95 +1,11 @@
|
||||
use graphene_std::Color;
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::text::{Font, FontCache};
|
||||
|
||||
/// Proportional share (0-1) for the document panel's side when splitting adjacent to non-document panels.
|
||||
const DOCUMENT_PANEL_SHARE: f64 = 0.8;
|
||||
/// Proportional share for each side when neither (or both) contain the document panel.
|
||||
const EQUAL_PANEL_SHARE: f64 = 0.5;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CachedData {
|
||||
pub font_cache: FontCache,
|
||||
pub font_catalog: FontCatalog,
|
||||
}
|
||||
|
||||
// TODO: Should this be a BTreeMap instead?
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FontCatalog(pub Vec<FontCatalogFamily>);
|
||||
|
||||
impl FontCatalog {
|
||||
pub fn find_font_style_in_catalog(&self, font: &Font) -> Option<FontCatalogStyle> {
|
||||
let family = self.0.iter().find(|family| family.name == font.font_family);
|
||||
|
||||
let found_style = family.map(|family| {
|
||||
let FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(&font.font_style, "");
|
||||
family.closest_style(weight, italic).clone()
|
||||
});
|
||||
|
||||
if found_style.is_none() {
|
||||
log::warn!("Font not found in catalog: {:?}", font);
|
||||
}
|
||||
|
||||
found_style
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(from_wasm_abi))]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FontCatalogFamily {
|
||||
/// The font family name.
|
||||
pub name: String,
|
||||
/// The font styles (variants) available for the font family.
|
||||
pub styles: Vec<FontCatalogStyle>,
|
||||
}
|
||||
|
||||
impl FontCatalogFamily {
|
||||
/// Finds the closest style to the given weight and italic setting.
|
||||
/// Aims to find the nearest weight while maintaining the italic setting if possible, but italic may change if no other option is available.
|
||||
pub fn closest_style(&self, weight: u32, italic: bool) -> &FontCatalogStyle {
|
||||
self.styles
|
||||
.iter()
|
||||
.map(|style| ((style.weight as i32 - weight as i32).unsigned_abs() + 10000 * (style.italic != italic) as u32, style))
|
||||
.min_by_key(|(distance, _)| *distance)
|
||||
.map(|(_, style)| style)
|
||||
.unwrap_or(&self.styles[0])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FontCatalogStyle {
|
||||
pub weight: u32,
|
||||
pub italic: bool,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
impl FontCatalogStyle {
|
||||
pub fn to_named_style(&self) -> String {
|
||||
let weight = self.weight;
|
||||
let italic = self.italic;
|
||||
|
||||
let named_weight = Font::named_weight(weight);
|
||||
let maybe_italic = if italic { " Italic" } else { "" };
|
||||
|
||||
format!("{named_weight}{maybe_italic} ({weight})")
|
||||
}
|
||||
|
||||
pub fn from_named_style(named_style: &str, url: impl Into<String>) -> FontCatalogStyle {
|
||||
let weight = named_style.split_terminator(['(', ')']).next_back().and_then(|x| x.parse::<u32>().ok()).unwrap_or(400);
|
||||
let italic = named_style.contains("Italic (");
|
||||
FontCatalogStyle { weight, italic, url: url.into() }
|
||||
}
|
||||
|
||||
/// Get the URL for the stylesheet for loading a font preview for this style of the given family name, subsetted to only the letters in the family name.
|
||||
pub fn preview_url(&self, family: impl Into<String>) -> String {
|
||||
let name = family.into().replace(' ', "+");
|
||||
let italic = if self.italic { "ital," } else { "" };
|
||||
let weight = self.weight;
|
||||
format!("https://fonts.googleapis.com/css2?display=swap&family={name}:{italic}wght@{weight}&text={name}")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(from_wasm_abi))]
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum PanelType {
|
||||
|
||||
@@ -30,6 +30,7 @@ pub use crate::messages::portfolio::document::overlays::{OverlaysMessage, Overla
|
||||
pub use crate::messages::portfolio::document::properties_panel::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant, PropertiesPanelMessageHandler};
|
||||
pub use crate::messages::portfolio::document::resource::{ResourceMessage, ResourceMessageContext, ResourceMessageDiscriminant, ResourceMessageHandler};
|
||||
pub use crate::messages::portfolio::document::{DocumentMessage, DocumentMessageContext, DocumentMessageDiscriminant, DocumentMessageHandler};
|
||||
pub use crate::messages::portfolio::fonts::{FontsMessage, FontsMessageContext, FontsMessageDiscriminant, FontsMessageHandler};
|
||||
pub use crate::messages::portfolio::persistent_state::{PersistentStateMessage, PersistentStateMessageContext, PersistentStateMessageDiscriminant, PersistentStateMessageHandler};
|
||||
pub use crate::messages::portfolio::{PortfolioMessage, PortfolioMessageContext, PortfolioMessageDiscriminant, PortfolioMessageHandler};
|
||||
pub use crate::messages::preferences::{PreferencesMessage, PreferencesMessageDiscriminant, PreferencesMessageHandler};
|
||||
|
||||
@@ -431,15 +431,21 @@ pub fn get_grid_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn
|
||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::grid::IDENTIFIER))
|
||||
}
|
||||
|
||||
/// Gets properties from the Text node
|
||||
pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<(&String, &Font, TypesettingConfig, bool)> {
|
||||
/// Gets properties from the Text node. Resolves the font selection by reading the resource id and lookup via the fonts message handler.
|
||||
pub fn get_text<'a>(
|
||||
layer: LayerNodeIdentifier,
|
||||
network_interface: &'a NodeNetworkInterface,
|
||||
fonts: &FontsMessageHandler,
|
||||
resources: &ResourceMessageHandler,
|
||||
) -> Option<(&'a String, Font, TypesettingConfig, bool)> {
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER))?;
|
||||
|
||||
let Some(TaggedValue::String(text)) = &inputs[graphene_std::text::text::TextInput::INDEX].as_value() else {
|
||||
return None;
|
||||
};
|
||||
let Some(TaggedValue::Font(font)) = &inputs[graphene_std::text::text::FontInput::INDEX].as_value() else {
|
||||
return None;
|
||||
let font = match &inputs[graphene_std::text::text::FontInput::INDEX].as_value() {
|
||||
Some(TaggedValue::Resource(resource_id)) => fonts.id_font(resources, *resource_id).unwrap_or_default(),
|
||||
_ => Font::default(),
|
||||
};
|
||||
let Some(&TaggedValue::F64(font_size)) = inputs[graphene_std::text::text::SizeInput::INDEX].as_value() else {
|
||||
return None;
|
||||
|
||||
@@ -16,7 +16,6 @@ use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::list::List;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::subpath::{Bezier, BezierHandles};
|
||||
use graphene_std::text::FontCache;
|
||||
use graphene_std::vector::algorithms::bezpath_algorithms::pathseg_compute_lookup_table;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point};
|
||||
use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModification, VectorModificationType};
|
||||
@@ -63,17 +62,18 @@ where
|
||||
}
|
||||
|
||||
/// Calculates the bounding box of the layer's text, based on the settings for max width and height specified in the typesetting config.
|
||||
pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, font_cache: &FontCache) -> Quad {
|
||||
pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, fonts: &FontsMessageHandler, responses: &mut VecDeque<Message>) -> Quad {
|
||||
// Use the `editor:text_frame` attribute if available (handles multi-item glyphs and the 'Index Elements' node)
|
||||
if let Some(&frame) = document.metadata().text_frames.get(&layer) {
|
||||
return frame * Quad::from_box([DVec2::ZERO, DVec2::ONE]);
|
||||
}
|
||||
|
||||
// Fallback: recompute from text content (e.g. layer hasn't rendered yet)
|
||||
let Some((text, font, typesetting, _)) = get_text(layer, &document.network_interface) else {
|
||||
let Some((text, font, typesetting, _)) = get_text(layer, &document.network_interface, fonts, &document.resources) else {
|
||||
return Quad::from_box([DVec2::ZERO, DVec2::ZERO]);
|
||||
};
|
||||
let far = graphene_std::text::bounding_box(text, font, font_cache, typesetting, false);
|
||||
let font = fonts.get_resource_or_queue_load(&font, responses);
|
||||
let far = graphene_std::text::bounding_box(text, &font, typesetting, false);
|
||||
Quad::from_box([DVec2::ZERO, far])
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ use super::utility_types::{ToolActionMessageContext, ToolFsmState, tool_message_
|
||||
use crate::application::generate_uuid;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayProvider;
|
||||
use crate::messages::portfolio::utility_types::CachedData;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::transform_layer::transform_layer_message_handler::TransformLayerMessageContext;
|
||||
use crate::messages::tool::utility_types::{HintData, ToolType};
|
||||
@@ -19,7 +18,7 @@ pub struct ToolMessageContext<'a> {
|
||||
pub document_id: DocumentId,
|
||||
pub document: &'a mut DocumentMessageHandler,
|
||||
pub input: &'a InputPreprocessorMessageHandler,
|
||||
pub cached_data: &'a CachedData,
|
||||
pub fonts: &'a FontsMessageHandler,
|
||||
pub node_graph: &'a NodeGraphExecutor,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
pub viewport: &'a ViewportMessageHandler,
|
||||
@@ -40,7 +39,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
||||
document_id,
|
||||
document,
|
||||
input,
|
||||
cached_data,
|
||||
fonts,
|
||||
node_graph,
|
||||
preferences,
|
||||
viewport,
|
||||
@@ -127,7 +126,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
||||
node_graph,
|
||||
preferences,
|
||||
viewport,
|
||||
cached_data,
|
||||
fonts,
|
||||
};
|
||||
|
||||
if let Some(tool_abort_message) = tool.event_to_message_map().tool_abort {
|
||||
@@ -218,7 +217,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
||||
tool_data.tools.get(active_tool).unwrap().activate(responses);
|
||||
|
||||
// Register initial properties
|
||||
tool_data.tools.get(active_tool).unwrap().refresh_options(responses, cached_data);
|
||||
tool_data.tools.get(active_tool).unwrap().refresh_options(responses);
|
||||
|
||||
// Notify the frontend about the initial active tool
|
||||
tool_data.send_layout(responses, LayoutTarget::ToolShelf, preferences.brush_tool);
|
||||
@@ -235,7 +234,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
||||
node_graph,
|
||||
preferences,
|
||||
viewport,
|
||||
cached_data,
|
||||
fonts,
|
||||
};
|
||||
|
||||
// Set initial hints and cursor
|
||||
@@ -259,7 +258,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
||||
ToolMessage::RefreshToolOptions => {
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
|
||||
tool_data.tools.get(&tool_data.active_tool_type).unwrap().refresh_options(responses, cached_data);
|
||||
tool_data.tools.get(&tool_data.active_tool_type).unwrap().refresh_options(responses);
|
||||
}
|
||||
ToolMessage::RefreshToolShelf => {
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
@@ -347,7 +346,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
||||
node_graph,
|
||||
preferences,
|
||||
viewport,
|
||||
cached_data,
|
||||
fonts,
|
||||
};
|
||||
if matches!(tool_message, ToolMessage::UpdateHints) {
|
||||
if graph_view_overlay_open {
|
||||
|
||||
@@ -720,13 +720,7 @@ impl Fsm for SelectToolFsmState {
|
||||
type ToolOptions = ();
|
||||
|
||||
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
|
||||
let ToolActionMessageContext {
|
||||
document,
|
||||
input,
|
||||
viewport,
|
||||
cached_data,
|
||||
..
|
||||
} = tool_action_data;
|
||||
let ToolActionMessageContext { document, input, viewport, fonts, .. } = tool_action_data;
|
||||
|
||||
let ToolMessage::Select(event) = event else { return self };
|
||||
match (self, event) {
|
||||
@@ -751,7 +745,7 @@ impl Fsm for SelectToolFsmState {
|
||||
|
||||
if document.metadata().is_text_layer(layer) {
|
||||
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
||||
let transformed_quad = layer_to_viewport * text_bounding_box(layer, document, &cached_data.font_cache);
|
||||
let transformed_quad = layer_to_viewport * text_bounding_box(layer, document, fonts, responses);
|
||||
overlay_context.dashed_quad(transformed_quad, None, None, Some(7.), Some(5.), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Transf
|
||||
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;
|
||||
use crate::messages::portfolio::utility_types::{CachedData, FontCatalog, FontCatalogStyle};
|
||||
use crate::messages::portfolio::fonts::utility_types::{FontCatalog, FontCatalogStyle};
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::color_selector::{
|
||||
ToolColorOptions, apply_fill_only_color_pick, apply_fill_only_enabled, refresh_slot_working_color, selection_changed_since_last_sync, solid, sync_fill_only,
|
||||
@@ -17,12 +17,13 @@ use crate::messages::tool::common_functionality::snapping::{self, SnapCandidateP
|
||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
use crate::messages::tool::common_functionality::utility_functions::text_bounding_box;
|
||||
use crate::messages::tool::utility_types::ToolRefreshOptions;
|
||||
use graph_craft::application_io::resource::ResourceId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::choice_type::ChoiceTypeStatic;
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::{Font, FontCache, TextAlign, TypesettingConfig, lines_clipping};
|
||||
use graphene_std::text::{Font, TextAlign, TypesettingConfig, lines_clipping};
|
||||
use graphene_std::vector::style::{Fill, FillChoice, FillChoiceUI};
|
||||
use graphene_std::{Color, NodeInputDecleration};
|
||||
|
||||
@@ -105,31 +106,30 @@ impl ToolMetadata for TextTool {
|
||||
}
|
||||
|
||||
fn create_text_widgets(tool: &TextTool, font_catalog: &FontCatalog, document: &DocumentMessageHandler) -> Vec<WidgetInstance> {
|
||||
// If a single text layer is selected, the control bar's font/style menus drive that layer's text node directly, going through the
|
||||
// same code path as the Properties panel (LoadFontData + SetInputValue, with closest_style and font_style_to_restore bookkeeping).
|
||||
// Otherwise the menus only update the control bar option for the next created text.
|
||||
let text_node_id = can_edit_selected(document).and_then(|layer| graph_modification_utils::get_text_id(layer, &document.network_interface));
|
||||
|
||||
let font_input_index = graphene_std::text::text::FontInput::INDEX;
|
||||
let apply_font = move |new_font: Font| -> Message {
|
||||
let apply_font = move |font: Font| -> Message {
|
||||
match text_node_id {
|
||||
Some(node_id) => NodeGraphMessage::SetInputValue {
|
||||
node_id,
|
||||
input_index: font_input_index,
|
||||
value: TaggedValue::Font(new_font),
|
||||
Some(node_id) => {
|
||||
let resource_id = ResourceId::new();
|
||||
Message::Batched {
|
||||
messages: Box::new([
|
||||
DocumentMessage::Resource(ResourceMessage::AddFont { resource_id, font }).into(),
|
||||
NodeGraphMessage::SetInputValue {
|
||||
node_id,
|
||||
input_index: graphene_std::text::text::FontInput::INDEX,
|
||||
value: TaggedValue::Resource(resource_id),
|
||||
}
|
||||
.into(),
|
||||
]),
|
||||
}
|
||||
}
|
||||
.into(),
|
||||
None => TextToolMessage::UpdateOptions {
|
||||
options: TextOptionsUpdate::Font { font: new_font },
|
||||
options: TextOptionsUpdate::Font { font },
|
||||
}
|
||||
.into(),
|
||||
}
|
||||
};
|
||||
let preview_font = move |new_font: Font| -> Message {
|
||||
Message::Batched {
|
||||
messages: Box::new([PortfolioMessage::LoadFontData { font: new_font.clone() }.into(), apply_font(new_font)]),
|
||||
}
|
||||
};
|
||||
let commit_font = move |new_font: Font| -> Message {
|
||||
match text_node_id {
|
||||
Some(_) => DeferMessage::AfterGraphRun {
|
||||
@@ -142,35 +142,27 @@ fn create_text_widgets(tool: &TextTool, font_catalog: &FontCatalog, document: &D
|
||||
|
||||
let font = DropdownInput::new(vec![
|
||||
font_catalog
|
||||
.0
|
||||
.iter()
|
||||
.map(|family| {
|
||||
let current_font = &tool.options.font;
|
||||
let mut new_font = Font::new(family.name.clone(), current_font.font_style_to_restore.clone().unwrap_or_else(|| current_font.font_style.clone()));
|
||||
new_font.font_style_to_restore = current_font.font_style_to_restore.clone().or_else(|| Some(new_font.font_style.clone()));
|
||||
let FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(&new_font.font_style, "");
|
||||
new_font.font_style = family.closest_style(weight, italic).to_named_style();
|
||||
|
||||
// Intentionally drop `font_style_to_restore` on commit so the committed style becomes the new basis for
|
||||
// subsequent family switches. Preserving the original style intent is hover-only behavior (handled by `new_font`).
|
||||
let FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(¤t_font.font_style, "");
|
||||
let commit_only_font = Font::new(family.name.clone(), family.closest_style(weight, italic).to_named_style());
|
||||
let new_font = Font::new(family.name.clone(), family.closest_style(weight, italic).to_named_style());
|
||||
let commit_only_font = new_font.clone();
|
||||
|
||||
MenuListEntry::new(family.name.clone())
|
||||
.label(family.name.clone())
|
||||
.font(family.closest_style(400, false).preview_url(&family.name))
|
||||
.on_update(move |_| preview_font(new_font.clone()))
|
||||
.on_update(move |_| apply_font(new_font.clone()))
|
||||
.on_commit(move |_| commit_font(commit_only_font.clone()))
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
])
|
||||
.selected_index(font_catalog.0.iter().position(|family| family.name == tool.options.font.font_family).map(|i| i as u32))
|
||||
.selected_index(font_catalog.iter().position(|family| family.name == tool.options.font.font_family).map(|i| i as u32))
|
||||
.virtual_scrolling(true)
|
||||
.widget_instance();
|
||||
|
||||
let style = DropdownInput::new({
|
||||
font_catalog
|
||||
.0
|
||||
.iter()
|
||||
.find(|family| family.name == tool.options.font.font_family)
|
||||
.map(|family| {
|
||||
@@ -182,7 +174,7 @@ fn create_text_widgets(tool: &TextTool, font_catalog: &FontCatalog, document: &D
|
||||
|
||||
MenuListEntry::new(font_style.clone())
|
||||
.label(font_style)
|
||||
.on_update(move |_| preview_font(new_font.clone()))
|
||||
.on_update(move |_| apply_font(new_font.clone()))
|
||||
.on_commit(move |_| commit_font(new_font_for_commit.clone()))
|
||||
};
|
||||
|
||||
@@ -196,7 +188,6 @@ fn create_text_widgets(tool: &TextTool, font_catalog: &FontCatalog, document: &D
|
||||
})
|
||||
.selected_index(
|
||||
font_catalog
|
||||
.0
|
||||
.iter()
|
||||
.find(|family| family.name == tool.options.font.font_family)
|
||||
.and_then(|family| {
|
||||
@@ -253,7 +244,7 @@ fn create_text_widgets(tool: &TextTool, font_catalog: &FontCatalog, document: &D
|
||||
}
|
||||
|
||||
impl ToolRefreshOptions for TextTool {
|
||||
fn refresh_options(&self, responses: &mut VecDeque<Message>, _cached_data: &CachedData) {
|
||||
fn refresh_options(&self, responses: &mut VecDeque<Message>) {
|
||||
// Defer to the SelectionChanged handler which has document context, required for the font/style
|
||||
// dropdowns to bind to the selected text layer's node graph inputs
|
||||
responses.add(TextToolMessage::SelectionChanged);
|
||||
@@ -303,7 +294,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Text
|
||||
ToolMessage::Text(TextToolMessage::UpdateOptions { options }) => options,
|
||||
ToolMessage::Text(TextToolMessage::SelectionChanged) => {
|
||||
if let Some(layer) = can_edit_selected(context.document)
|
||||
&& let Some((_, font, typesetting, _)) = graph_modification_utils::get_text(layer, &context.document.network_interface)
|
||||
&& let Some((_, font, typesetting, _)) = graph_modification_utils::get_text(layer, &context.document.network_interface, context.fonts, &context.document.resources)
|
||||
{
|
||||
self.options.align = typesetting.align;
|
||||
self.options.font_size = typesetting.font_size;
|
||||
@@ -311,7 +302,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Text
|
||||
if let Some(editing_text) = self.tool_data.editing_text.as_mut() {
|
||||
editing_text.typesetting.align = typesetting.align;
|
||||
editing_text.typesetting.font_size = typesetting.font_size;
|
||||
editing_text.font = font.clone();
|
||||
editing_text.font = font;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +317,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Text
|
||||
// Text tool has no fill checkbox; keep enabled so new text never starts with `None`
|
||||
self.options.fill.enabled = Some(true);
|
||||
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions, &context.cached_data.font_catalog, context.document);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions, &context.fonts.font_catalog, context.document);
|
||||
return;
|
||||
}
|
||||
_ => {
|
||||
@@ -385,7 +376,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Text
|
||||
}
|
||||
}
|
||||
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions, &context.cached_data.font_catalog, context.document);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions, &context.fonts.font_catalog, context.document);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
@@ -476,9 +467,9 @@ struct TextToolData {
|
||||
}
|
||||
|
||||
impl TextToolData {
|
||||
fn delete_empty_layer(&mut self, font_cache: &FontCache, responses: &mut VecDeque<Message>) -> TextToolFsmState {
|
||||
fn delete_empty_layer(&mut self, fonts: &FontsMessageHandler, responses: &mut VecDeque<Message>) -> TextToolFsmState {
|
||||
// Remove the editable textbox UI first
|
||||
self.set_editing(false, font_cache, responses);
|
||||
self.set_editing(false, fonts, responses);
|
||||
|
||||
// Delete the empty text layer and update the graph
|
||||
responses.add(NodeGraphMessage::DeleteNodes {
|
||||
@@ -491,15 +482,16 @@ impl TextToolData {
|
||||
}
|
||||
|
||||
/// Set the editing state of the currently modifying layer
|
||||
fn set_editing(&self, editable: bool, font_cache: &FontCache, responses: &mut VecDeque<Message>) {
|
||||
fn set_editing(&self, editable: bool, fonts: &FontsMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if let Some(editing_text) = self.editing_text.as_ref().filter(|_| editable) {
|
||||
let (align, align_last) = editing_text.typesetting.align.css();
|
||||
let font_data = fonts.get_resource_or_queue_load(&editing_text.font, responses).as_ref().to_vec().into();
|
||||
responses.add(FrontendMessage::DisplayEditableTextbox {
|
||||
text: editing_text.text.clone(),
|
||||
line_height_ratio: editing_text.typesetting.line_height_ratio,
|
||||
font_size: editing_text.typesetting.font_size,
|
||||
color: editing_text.color.map_or("#000000".to_string(), |color| SRGBA8::from(color).to_css_hex()),
|
||||
font_data: font_cache.get(&editing_text.font).map(|(data, _)| data.clone()).unwrap_or_default().into(),
|
||||
font_data,
|
||||
transform: editing_text.transform.to_cols_array(),
|
||||
max_width: editing_text.typesetting.max_width,
|
||||
max_height: editing_text.typesetting.max_height,
|
||||
@@ -517,13 +509,13 @@ impl TextToolData {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_layer_text_node(&mut self, document: &DocumentMessageHandler) -> Option<()> {
|
||||
fn load_layer_text_node(&mut self, document: &DocumentMessageHandler, fonts: &FontsMessageHandler) -> Option<()> {
|
||||
let transform = document.metadata().transform_to_viewport(self.layer);
|
||||
let color = graph_modification_utils::get_fill_color(self.layer, &document.network_interface).unwrap_or(Color::BLACK);
|
||||
let (text, font, typesetting, _) = graph_modification_utils::get_text(self.layer, &document.network_interface)?;
|
||||
let (text, font, typesetting, _) = graph_modification_utils::get_text(self.layer, &document.network_interface, fonts, &document.resources)?;
|
||||
self.editing_text = Some(EditingText {
|
||||
text: text.clone(),
|
||||
font: font.clone(),
|
||||
font,
|
||||
typesetting,
|
||||
color: Some(color),
|
||||
transform,
|
||||
@@ -532,20 +524,20 @@ impl TextToolData {
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn start_editing_layer(&mut self, layer: LayerNodeIdentifier, tool_state: TextToolFsmState, document: &DocumentMessageHandler, font_cache: &FontCache, responses: &mut VecDeque<Message>) {
|
||||
fn start_editing_layer(&mut self, layer: LayerNodeIdentifier, tool_state: TextToolFsmState, document: &DocumentMessageHandler, fonts: &FontsMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot edit ROOT_PARENT in TextTooLData")
|
||||
}
|
||||
|
||||
if tool_state == TextToolFsmState::Editing {
|
||||
self.set_editing(false, font_cache, responses);
|
||||
self.set_editing(false, fonts, responses);
|
||||
}
|
||||
|
||||
self.layer = layer;
|
||||
if self.load_layer_text_node(document).is_some() {
|
||||
if self.load_layer_text_node(document, fonts).is_some() {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
self.set_editing(true, font_cache, responses);
|
||||
self.set_editing(true, fonts, responses);
|
||||
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] });
|
||||
// Make the rendered text invisible while editing
|
||||
@@ -557,14 +549,16 @@ impl TextToolData {
|
||||
};
|
||||
}
|
||||
|
||||
fn new_text(&mut self, document: &DocumentMessageHandler, editing_text: EditingText, font_cache: &FontCache, responses: &mut VecDeque<Message>) {
|
||||
// Create new text
|
||||
fn new_text(&mut self, document: &DocumentMessageHandler, editing_text: EditingText, fonts: &FontsMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
self.new_text = String::new();
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
self.layer = LayerNodeIdentifier::new_unchecked(NodeId::new());
|
||||
|
||||
responses.add(PortfolioMessage::LoadFontData { font: editing_text.font.clone() });
|
||||
responses.add(FontsMessage::Load {
|
||||
font: editing_text.font.clone(),
|
||||
response: Box::new(NodeGraphMessage::RunDocumentGraph.into()),
|
||||
});
|
||||
responses.add(GraphOperationMessage::NewTextLayer {
|
||||
id: self.layer.to_node(),
|
||||
text: String::new(),
|
||||
@@ -580,7 +574,7 @@ impl TextToolData {
|
||||
let transform = editing_text.transform;
|
||||
self.editing_text = Some(editing_text);
|
||||
|
||||
self.set_editing(true, font_cache, responses);
|
||||
self.set_editing(true, fonts, responses);
|
||||
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] });
|
||||
|
||||
@@ -602,20 +596,23 @@ impl TextToolData {
|
||||
});
|
||||
}
|
||||
|
||||
fn check_click(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, font_cache: &FontCache) -> Option<LayerNodeIdentifier> {
|
||||
document.metadata().all_layers().filter(|&layer| document.metadata().is_text_layer(layer)).find(|&layer| {
|
||||
let transformed_quad = document.metadata().transform_to_viewport(layer) * text_bounding_box(layer, document, font_cache);
|
||||
let mouse = DVec2::new(input.mouse.position.x, input.mouse.position.y);
|
||||
|
||||
transformed_quad.contains(mouse)
|
||||
})
|
||||
fn check_click(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, fonts: &FontsMessageHandler, responses: &mut VecDeque<Message>) -> Option<LayerNodeIdentifier> {
|
||||
let mouse = DVec2::new(input.mouse.position.x, input.mouse.position.y);
|
||||
let layers: Vec<LayerNodeIdentifier> = document.metadata().all_layers().filter(|&layer| document.metadata().is_text_layer(layer)).collect();
|
||||
for layer in layers {
|
||||
let transformed_quad = document.metadata().transform_to_viewport(layer) * text_bounding_box(layer, document, fonts, responses);
|
||||
if transformed_quad.contains(mouse) {
|
||||
return Some(layer);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn get_snap_candidates(&mut self, document: &DocumentMessageHandler, font_cache: &FontCache) {
|
||||
fn get_snap_candidates(&mut self, document: &DocumentMessageHandler, fonts: &FontsMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
self.snap_candidates.clear();
|
||||
|
||||
if let Some(ResizingLayer { id, .. }) = self.layer_dragging {
|
||||
let quad = document.metadata().transform_to_document(id) * text_bounding_box(id, document, font_cache);
|
||||
let quad = document.metadata().transform_to_document(id) * text_bounding_box(id, document, fonts, responses);
|
||||
snapping::get_bbox_points(quad, &mut self.snap_candidates, snapping::BBoxSnapValues::BOUNDING_BOX, document);
|
||||
}
|
||||
}
|
||||
@@ -651,14 +648,7 @@ impl Fsm for TextToolFsmState {
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
let ToolActionMessageContext {
|
||||
document,
|
||||
input,
|
||||
cached_data,
|
||||
viewport,
|
||||
..
|
||||
} = transition_data;
|
||||
let font_cache = &cached_data.font_cache;
|
||||
let ToolActionMessageContext { document, input, fonts, viewport, .. } = transition_data;
|
||||
let fill_color = COLOR_OVERLAY_BLUE_05;
|
||||
|
||||
let ToolMessage::Text(event) = event else { return self };
|
||||
@@ -667,7 +657,8 @@ impl Fsm for TextToolFsmState {
|
||||
let transform = document.metadata().transform_to_viewport(tool_data.layer).to_cols_array();
|
||||
responses.add(FrontendMessage::DisplayEditableTextboxTransform { transform });
|
||||
if let Some(editing_text) = tool_data.editing_text.as_mut() {
|
||||
let far = graphene_std::text::bounding_box(&tool_data.new_text, &editing_text.font, font_cache, editing_text.typesetting, false);
|
||||
let font_resource = fonts.get_resource_or_queue_load(&editing_text.font, responses);
|
||||
let far = graphene_std::text::bounding_box(&tool_data.new_text, &font_resource, editing_text.typesetting, false);
|
||||
if far.x != 0. && far.y != 0. {
|
||||
let quad = Quad::from_box([DVec2::ZERO, far]);
|
||||
let transformed_quad = document.metadata().transform_to_viewport(tool_data.layer) * quad;
|
||||
@@ -694,7 +685,7 @@ impl Fsm for TextToolFsmState {
|
||||
let selected = document.network_interface.selected_nodes();
|
||||
let mut all_layers = selected.selected_visible_and_unlocked_layers(&document.network_interface);
|
||||
let layer = all_layers.find(|&layer| document.metadata().is_text_layer(layer));
|
||||
let bounds = layer.map(|layer| text_bounding_box(layer, document, font_cache));
|
||||
let bounds = layer.map(|layer| text_bounding_box(layer, document, fonts, responses));
|
||||
let layer_transform = layer.map(|layer| document.metadata().transform_to_viewport(layer)).unwrap_or(DAffine2::IDENTITY);
|
||||
|
||||
if layer.is_none() || bounds.is_none() || layer_transform.matrix2.determinant() == 0. {
|
||||
@@ -710,10 +701,11 @@ impl Fsm for TextToolFsmState {
|
||||
bounding_box_manager.render_quad(&mut overlay_context);
|
||||
// Draw red overlay if text is clipped
|
||||
let transformed_quad = layer_transform * bounds;
|
||||
if let Some((text, font, typesetting, _)) = graph_modification_utils::get_text(layer.unwrap(), &document.network_interface)
|
||||
&& lines_clipping(text.as_str(), font, font_cache, typesetting)
|
||||
{
|
||||
overlay_context.line(transformed_quad.0[2], transformed_quad.0[3], Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
if let Some((text, font, typesetting, _)) = graph_modification_utils::get_text(layer.unwrap(), &document.network_interface, fonts, &document.resources) {
|
||||
let font_resource = fonts.get_resource_or_queue_load(&font, responses);
|
||||
if lines_clipping(text.as_str(), &font_resource, typesetting) {
|
||||
overlay_context.line(transformed_quad.0[2], transformed_quad.0[3], Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
}
|
||||
}
|
||||
|
||||
bounding_box_manager.render_overlays(&mut overlay_context, false);
|
||||
@@ -728,7 +720,7 @@ impl Fsm for TextToolFsmState {
|
||||
}
|
||||
(state, TextToolMessage::EditSelected) => {
|
||||
if let Some(layer) = can_edit_selected(document) {
|
||||
tool_data.start_editing_layer(layer, state, document, font_cache, responses);
|
||||
tool_data.start_editing_layer(layer, state, document, fonts, responses);
|
||||
return TextToolFsmState::Editing;
|
||||
}
|
||||
|
||||
@@ -769,10 +761,10 @@ impl Fsm for TextToolFsmState {
|
||||
bounds.original_bound_transform = bounds.transform;
|
||||
bounds.center_of_transformation = bounds.transform.transform_point2((bounds.bounds[0] + bounds.bounds[1]) / 2.);
|
||||
}
|
||||
tool_data.get_snap_candidates(document, font_cache);
|
||||
tool_data.get_snap_candidates(document, fonts, responses);
|
||||
|
||||
return TextToolFsmState::ResizingBounds;
|
||||
} else if let Some(clicked_layer) = TextToolData::check_click(document, input, font_cache) {
|
||||
} else if let Some(clicked_layer) = TextToolData::check_click(document, input, fonts, responses) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
if selected != Some(clicked_layer) {
|
||||
@@ -784,7 +776,7 @@ impl Fsm for TextToolFsmState {
|
||||
id: clicked_layer,
|
||||
original_transform,
|
||||
});
|
||||
tool_data.get_snap_candidates(document, font_cache);
|
||||
tool_data.get_snap_candidates(document, fonts, responses);
|
||||
return TextToolFsmState::Dragging;
|
||||
}
|
||||
TextToolFsmState::Placing
|
||||
@@ -967,8 +959,8 @@ impl Fsm for TextToolFsmState {
|
||||
let has_dragged = (start - end).length_squared() > DRAG_THRESHOLD * DRAG_THRESHOLD;
|
||||
|
||||
// Check if the user has clicked (no dragging) on some existing text
|
||||
if !has_dragged && let Some(clicked_text_layer_path) = TextToolData::check_click(document, input, font_cache) {
|
||||
tool_data.start_editing_layer(clicked_text_layer_path, self, document, font_cache, responses);
|
||||
if !has_dragged && let Some(clicked_text_layer_path) = TextToolData::check_click(document, input, fonts, responses) {
|
||||
tool_data.start_editing_layer(clicked_text_layer_path, self, document, fonts, responses);
|
||||
return TextToolFsmState::Editing;
|
||||
}
|
||||
|
||||
@@ -989,7 +981,7 @@ impl Fsm for TextToolFsmState {
|
||||
font: Font::new(tool_options.font.font_family.clone(), tool_options.font.font_style.clone()),
|
||||
color: tool_options.fill.active_color(),
|
||||
};
|
||||
tool_data.new_text(document, editing_text, font_cache, responses);
|
||||
tool_data.new_text(document, editing_text, fonts, responses);
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
(TextToolFsmState::Dragging, TextToolMessage::DragStop) => {
|
||||
@@ -1004,7 +996,7 @@ impl Fsm for TextToolFsmState {
|
||||
}
|
||||
|
||||
if drag_too_small && let Some(layer_info) = &tool_data.layer_dragging {
|
||||
tool_data.start_editing_layer(layer_info.id, self, document, font_cache, responses);
|
||||
tool_data.start_editing_layer(layer_info.id, self, document, fonts, responses);
|
||||
return TextToolFsmState::Editing;
|
||||
}
|
||||
tool_data.layer_dragging.take();
|
||||
@@ -1013,8 +1005,9 @@ impl Fsm for TextToolFsmState {
|
||||
}
|
||||
(TextToolFsmState::Editing, TextToolMessage::RefreshEditingFontData) => {
|
||||
let font = Font::new(tool_options.font.font_family.clone(), tool_options.font.font_style.clone());
|
||||
let font_resource = fonts.get_resource_or_queue_load(&font, responses);
|
||||
responses.add(FrontendMessage::DisplayEditableTextboxUpdateFontData {
|
||||
font_data: font_cache.get(&font).map(|(data, _)| data.clone()).unwrap_or_default().into(),
|
||||
font_data: font_resource.as_ref().to_vec().into(),
|
||||
});
|
||||
|
||||
TextToolFsmState::Editing
|
||||
@@ -1023,7 +1016,7 @@ impl Fsm for TextToolFsmState {
|
||||
tool_data.new_text = new_text;
|
||||
|
||||
if !is_left_or_right_click {
|
||||
tool_data.set_editing(false, font_cache, responses);
|
||||
tool_data.set_editing(false, fonts, responses);
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(graph_modification_utils::get_text_id(tool_data.layer, &document.network_interface).unwrap(), 1),
|
||||
@@ -1034,7 +1027,7 @@ impl Fsm for TextToolFsmState {
|
||||
TextToolFsmState::Ready
|
||||
} else {
|
||||
if tool_data.new_text.is_empty() {
|
||||
return tool_data.delete_empty_layer(font_cache, responses);
|
||||
return tool_data.delete_empty_layer(fonts, responses);
|
||||
}
|
||||
|
||||
responses.add(FrontendMessage::TriggerTextCommit);
|
||||
@@ -1055,7 +1048,7 @@ impl Fsm for TextToolFsmState {
|
||||
}
|
||||
(TextToolFsmState::Editing, TextToolMessage::Abort) => {
|
||||
if tool_data.new_text.is_empty() {
|
||||
return tool_data.delete_empty_layer(font_cache, responses);
|
||||
return tool_data.delete_empty_layer(fonts, responses);
|
||||
}
|
||||
|
||||
responses.add(FrontendMessage::TriggerTextCommit);
|
||||
|
||||
@@ -9,7 +9,6 @@ use crate::messages::input_mapper::utility_types::macros::action_shortcut;
|
||||
use crate::messages::input_mapper::utility_types::misc::ActionShortcut;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayProvider;
|
||||
use crate::messages::portfolio::utility_types::CachedData;
|
||||
use crate::messages::preferences::PreferencesMessageHandler;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeType;
|
||||
@@ -25,7 +24,7 @@ pub struct ToolActionMessageContext<'a> {
|
||||
pub document_id: DocumentId,
|
||||
pub global_tool_data: &'a DocumentToolData,
|
||||
pub input: &'a InputPreprocessorMessageHandler,
|
||||
pub cached_data: &'a CachedData,
|
||||
pub fonts: &'a FontsMessageHandler,
|
||||
pub shape_editor: &'a mut ShapeState,
|
||||
pub node_graph: &'a NodeGraphExecutor,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
@@ -38,11 +37,11 @@ impl<T> ToolCommon for T where T: for<'a, 'b> MessageHandler<ToolMessage, &'b mu
|
||||
type Tool = dyn ToolCommon + Send + Sync;
|
||||
|
||||
pub trait ToolRefreshOptions {
|
||||
fn refresh_options(&self, responses: &mut VecDeque<Message>, _cached_data: &CachedData);
|
||||
fn refresh_options(&self, responses: &mut VecDeque<Message>);
|
||||
}
|
||||
|
||||
impl<T: LayoutHolder> ToolRefreshOptions for T {
|
||||
fn refresh_options(&self, responses: &mut VecDeque<Message>, _cached_data: &CachedData) {
|
||||
fn refresh_options(&self, responses: &mut VecDeque<Message>) {
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig, TimingI
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::raster::{CPU, Raster};
|
||||
use graphene_std::renderer::RenderMetadata;
|
||||
use graphene_std::text::FontCache;
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::Vector;
|
||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
|
||||
@@ -95,10 +94,6 @@ impl NodeGraphExecutor {
|
||||
execution_id
|
||||
}
|
||||
|
||||
pub fn update_font_cache(&self, font_cache: FontCache) {
|
||||
self.runtime_io.send(GraphRuntimeRequest::FontCacheUpdate(font_cache)).expect("Failed to send font cache update");
|
||||
}
|
||||
|
||||
pub fn update_editor_preferences(&self, editor_preferences: EditorPreferences) {
|
||||
self.runtime_io
|
||||
.send(GraphRuntimeRequest::EditorPreferencesUpdate(editor_preferences))
|
||||
|
||||
@@ -17,7 +17,6 @@ use graphene_std::ops::Convert;
|
||||
use graphene_std::platform_application_io::canvas_utils::{Canvas, CanvasSurface, CanvasSurfaceHandle};
|
||||
use graphene_std::raster_types::Raster;
|
||||
use graphene_std::renderer::{Render, RenderParams, RenderSvgSegmentList, SvgRender, SvgSegment};
|
||||
use graphene_std::text::FontCache;
|
||||
use graphene_std::transform::RenderQuality;
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::vector::style::RenderMode;
|
||||
@@ -71,7 +70,6 @@ pub struct NodeRuntime {
|
||||
pub enum GraphRuntimeRequest {
|
||||
GraphUpdate(GraphUpdate),
|
||||
ExecutionRequest(ExecutionRequest),
|
||||
FontCacheUpdate(FontCache),
|
||||
EditorPreferencesUpdate(EditorPreferences),
|
||||
}
|
||||
|
||||
@@ -134,7 +132,6 @@ impl NodeRuntime {
|
||||
update_thumbnails: true,
|
||||
|
||||
editor_api: PlatformEditorApi {
|
||||
font_cache: FontCache::default(),
|
||||
editor_preferences: Box::new(EditorPreferences::default()),
|
||||
node_graph_message_sender: Box::new(InternalNodeGraphUpdateSender(sender)),
|
||||
|
||||
@@ -162,7 +159,6 @@ impl NodeRuntime {
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> Option<ImageTexture> {
|
||||
let mut font = None;
|
||||
let mut preferences = None;
|
||||
let mut graph = None;
|
||||
let mut eyedropper = None;
|
||||
@@ -186,7 +182,6 @@ impl NodeRuntime {
|
||||
break;
|
||||
}
|
||||
}
|
||||
GraphRuntimeRequest::FontCacheUpdate(_) => font = Some(request),
|
||||
GraphRuntimeRequest::EditorPreferencesUpdate(_) => preferences = Some(request),
|
||||
}
|
||||
}
|
||||
@@ -199,27 +194,13 @@ impl NodeRuntime {
|
||||
eyedropper.render_config.pointer = execution.render_config.pointer;
|
||||
}
|
||||
|
||||
let requests = [font, preferences, graph, eyedropper, execution].into_iter().flatten();
|
||||
let requests = [preferences, graph, eyedropper, execution].into_iter().flatten();
|
||||
|
||||
for request in requests {
|
||||
match request {
|
||||
GraphRuntimeRequest::FontCacheUpdate(font_cache) => {
|
||||
self.editor_api = PlatformEditorApi {
|
||||
font_cache,
|
||||
application_io: self.editor_api.application_io.clone(),
|
||||
node_graph_message_sender: Box::new(self.sender.clone()),
|
||||
editor_preferences: Box::new(self.editor_preferences.clone()),
|
||||
}
|
||||
.into();
|
||||
if let Some(graph) = self.old_graph.clone() {
|
||||
// We ignore this result as compilation errors should have been reported in an earlier iteration
|
||||
let _ = self.update_network(graph).await;
|
||||
}
|
||||
}
|
||||
GraphRuntimeRequest::EditorPreferencesUpdate(preferences) => {
|
||||
self.editor_preferences = preferences.clone();
|
||||
self.editor_api = PlatformEditorApi {
|
||||
font_cache: self.editor_api.font_cache.clone(),
|
||||
application_io: self.editor_api.application_io.clone(),
|
||||
node_graph_message_sender: Box::new(self.sender.clone()),
|
||||
editor_preferences: Box::new(preferences),
|
||||
@@ -587,7 +568,6 @@ pub(crate) fn replace_application_io(application_io: PlatformApplicationIo) {
|
||||
impl NodeRuntime {
|
||||
pub(crate) fn replace_application_io(&mut self, application_io: PlatformApplicationIo) {
|
||||
self.editor_api = PlatformEditorApi {
|
||||
font_cache: self.editor_api.font_cache.clone(),
|
||||
application_io: Some(application_io.into()),
|
||||
node_graph_message_sender: Box::new(self.sender.clone()),
|
||||
editor_preferences: Box::new(self.editor_preferences.clone()),
|
||||
|
||||
Reference in New Issue
Block a user