mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-18 18:28:08 +08:00
Font selection for text layers (#585)
* Add font dropdown * Add fonts * Font tool options * Fix tests * Replace http with https * Add variant selection * Do not embed default font * Use proxied font list API * Change default font to Merriweather * Remove outdated comment * Specify font once & load font into foreignobject * Fix tests * Rename variant to font_style * Change TextAreaInput to use FieldInput (WIP, breaks functionality) * Fix textarea functionality * Fix types * Add weight name mapping * Change labeling of "Italic" * Remove commented HTML node * Rename font "name" to "font_family" and "file" "font_file" * Fix errors * Fix fmt Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
parent
916a575446
commit
239aa03453
@@ -64,8 +64,8 @@ impl Dispatcher {
|
||||
#[remain::unsorted]
|
||||
NoOp => {}
|
||||
Frontend(message) => {
|
||||
// Image data should be immediatly handled
|
||||
if let FrontendMessage::UpdateImageData { .. } = message {
|
||||
// Image and font loading should be immediately handled
|
||||
if let FrontendMessage::UpdateImageData { .. } | FrontendMessage::TriggerFontLoad { .. } = message {
|
||||
self.responses.push(message);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":919378319526168453,"layer_ids":[919378319526168452],"layers":[{"visible":true,"name":null,"data":{"Shape":{"path":[{"MoveTo":{"x":0.0,"y":0.0}},{"LineTo":{"x":1.0,"y":0.0}},{"LineTo":{"x":1.0,"y":1.0}},{"LineTo":{"x":0.0,"y":1.0}},"ClosePath"],"style":{"stroke":null,"fill":{"Solid":{"red":0.0,"green":0.0,"blue":0.0,"alpha":1.0}}},"render_index":1,"closed":true}},"transform":{"matrix2":[303.890625,0.0,-0.0,362.10546875],"translation":[-148.83984375,-235.8828125]},"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[259.88359375,366.9]},"blend_mode":"Normal","opacity":1.0}},"saved_document_identifier":0,"name":"Untitled Document","layer_metadata":[[[919378319526168452],{"selected":true,"expanded":false}],[[],{"selected":false,"expanded":true}]],"layer_range_selection_reference":[919378319526168452],"movement_handler":{"pan":[-118.8,-45.60000000000001],"panning":false,"snap_tilt":false,"snap_tilt_released":false,"tilt":0.0,"tilting":false,"zoom":1.0,"zooming":false,"snap_zoom":false,"mouse_position":[0.0,0.0]},"artboard_message_handler":{"artboards_graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":0,"layer_ids":[],"layers":[]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[259.88359375,366.9]},"blend_mode":"Normal","opacity":1.0}},"artboard_ids":[]},"properties_panel_message_handler":{"active_selection":[[919378319526168452],"Artwork"]},"overlays_visible":true,"snapping_enabled":true,"view_mode":"Normal","version":"0.0.5"}
|
||||
{"graphene_document":{"font_cache": {"data":{},"default":null}, "root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":919378319526168453,"layer_ids":[919378319526168452],"layers":[{"visible":true,"name":null,"data":{"Shape":{"path":[{"MoveTo":{"x":0.0,"y":0.0}},{"LineTo":{"x":1.0,"y":0.0}},{"LineTo":{"x":1.0,"y":1.0}},{"LineTo":{"x":0.0,"y":1.0}},"ClosePath"],"style":{"stroke":null,"fill":{"Solid":{"red":0.0,"green":0.0,"blue":0.0,"alpha":1.0}}},"render_index":1,"closed":true}},"transform":{"matrix2":[303.890625,0.0,-0.0,362.10546875],"translation":[-148.83984375,-235.8828125]},"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[259.88359375,366.9]},"blend_mode":"Normal","opacity":1.0}},"saved_document_identifier":0,"name":"Untitled Document","layer_metadata":[[[919378319526168452],{"selected":true,"expanded":false}],[[],{"selected":false,"expanded":true}]],"layer_range_selection_reference":[919378319526168452],"movement_handler":{"pan":[-118.8,-45.60000000000001],"panning":false,"snap_tilt":false,"snap_tilt_released":false,"tilt":0.0,"tilting":false,"zoom":1.0,"zooming":false,"snap_zoom":false,"mouse_position":[0.0,0.0]},"artboard_message_handler":{"artboards_graphene_document":{"font_cache": {"data": {}}, "root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":0,"layer_ids":[],"layers":[]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[259.88359375,366.9]},"blend_mode":"Normal","opacity":1.0}},"artboard_ids":[]},"properties_panel_message_handler":{"active_selection":[[919378319526168452],"Artwork"]},"overlays_visible":true,"snapping_enabled":true,"view_mode":"Normal","version":"0.0.6"}
|
||||
|
||||
@@ -57,5 +57,5 @@ pub const FILE_EXPORT_SUFFIX: &str = ".svg";
|
||||
pub const COLOR_ACCENT: Color = Color::from_unsafe(0x00 as f32 / 255., 0xA8 as f32 / 255., 0xFF as f32 / 255.);
|
||||
|
||||
// Document
|
||||
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.5";
|
||||
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.6";
|
||||
pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05;
|
||||
|
||||
@@ -66,10 +66,18 @@ pub enum DocumentMessage {
|
||||
FolderChanged {
|
||||
affected_folder_path: Vec<LayerId>,
|
||||
},
|
||||
FontLoaded {
|
||||
font: String,
|
||||
data: Vec<u8>,
|
||||
is_default: bool,
|
||||
},
|
||||
GroupSelectedLayers,
|
||||
LayerChanged {
|
||||
affected_layer_path: Vec<LayerId>,
|
||||
},
|
||||
LoadFont {
|
||||
font: String,
|
||||
},
|
||||
MoveSelectedLayersTo {
|
||||
folder_path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
|
||||
@@ -162,7 +162,13 @@ impl DocumentMessageHandler {
|
||||
// TODO: Create VectorManipulatorShape when creating a kurbo shape as a stopgap, rather than on each new selection
|
||||
match &layer.ok()?.data {
|
||||
LayerDataType::Shape(shape) => Some(VectorShape::new(path_to_shape.to_vec(), viewport_transform, &shape.path, shape.closed, responses)),
|
||||
LayerDataType::Text(text) => Some(VectorShape::new(path_to_shape.to_vec(), viewport_transform, &text.to_bez_path_nonmut(), true, responses)),
|
||||
LayerDataType::Text(text) => Some(VectorShape::new(
|
||||
path_to_shape.to_vec(),
|
||||
viewport_transform,
|
||||
&text.to_bez_path_nonmut(&self.graphene_document.font_cache),
|
||||
true,
|
||||
responses,
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
@@ -421,7 +427,7 @@ impl DocumentMessageHandler {
|
||||
.get_mut(&path)
|
||||
.ok_or_else(|| EditorError::Document(format!("Could not get layer metadata for {:?}", path)))?;
|
||||
let layer = self.graphene_document.layer(&path)?;
|
||||
let entry = layer_panel_entry(&data, self.graphene_document.multiply_transforms(&path)?, layer, path);
|
||||
let entry = layer_panel_entry(&data, self.graphene_document.multiply_transforms(&path)?, layer, path, &self.graphene_document.font_cache);
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
@@ -442,7 +448,7 @@ impl DocumentMessageHandler {
|
||||
.ok()?;
|
||||
let layer = self.graphene_document.layer(path).ok()?;
|
||||
|
||||
Some(layer_panel_entry(layer_metadata, transform, layer, path.to_vec()))
|
||||
Some(layer_panel_entry(layer_metadata, transform, layer, path.to_vec(), &self.graphene_document.font_cache))
|
||||
}
|
||||
|
||||
/// When working with an insert index, deleting the layers may cause the insert index to point to a different location (if the layer being deleted was located before the insert index).
|
||||
@@ -477,12 +483,12 @@ impl DocumentMessageHandler {
|
||||
/// Creates the blob URLs for the image data in the document
|
||||
pub fn load_image_data(&self, responses: &mut VecDeque<Message>, root: &LayerDataType, mut path: Vec<LayerId>) {
|
||||
let mut image_data = Vec::new();
|
||||
fn walk_layers(data: &LayerDataType, path: &mut Vec<LayerId>, responses: &mut VecDeque<Message>, image_data: &mut Vec<FrontendImageData>) {
|
||||
fn walk_layers(data: &LayerDataType, path: &mut Vec<LayerId>, image_data: &mut Vec<FrontendImageData>) {
|
||||
match data {
|
||||
LayerDataType::Folder(f) => {
|
||||
for (id, layer) in f.layer_ids.iter().zip(f.layers().iter()) {
|
||||
path.push(*id);
|
||||
walk_layers(&layer.data, path, responses, image_data);
|
||||
walk_layers(&layer.data, path, image_data);
|
||||
path.pop();
|
||||
}
|
||||
}
|
||||
@@ -495,11 +501,17 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
walk_layers(root, &mut path, responses, &mut image_data);
|
||||
walk_layers(root, &mut path, &mut image_data);
|
||||
if !image_data.is_empty() {
|
||||
responses.push_front(FrontendMessage::UpdateImageData { image_data }.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_default_font(&self, responses: &mut VecDeque<Message>) {
|
||||
if !self.graphene_document.font_cache.has_default() {
|
||||
responses.push_back(FrontendMessage::TriggerDefaultFontLoad.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PropertyHolder for DocumentMessageHandler {
|
||||
@@ -897,6 +909,10 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
|
||||
let affected_layer_path = affected_folder_path;
|
||||
responses.extend([LayerChanged { affected_layer_path }.into(), DocumentStructureChanged.into()]);
|
||||
}
|
||||
FontLoaded { font, data, is_default } => {
|
||||
self.graphene_document.font_cache.insert(font, data, is_default);
|
||||
responses.push_back(DocumentMessage::DirtyRenderDocument.into());
|
||||
}
|
||||
GroupSelectedLayers => {
|
||||
let mut new_folder_path = self.graphene_document.shallowest_common_folder(self.selected_layers()).unwrap_or(&[]).to_vec();
|
||||
|
||||
@@ -932,6 +948,11 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
|
||||
}
|
||||
responses.push_back(PropertiesPanelMessage::CheckSelectedWasUpdated { path: affected_layer_path }.into());
|
||||
}
|
||||
LoadFont { font } => {
|
||||
if !self.graphene_document.font_cache.loaded_font(&font) {
|
||||
responses.push_front(FrontendMessage::TriggerFontLoad { font }.into());
|
||||
}
|
||||
}
|
||||
MoveSelectedLayersTo {
|
||||
folder_path,
|
||||
insert_index,
|
||||
@@ -1232,7 +1253,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
|
||||
let text = self.graphene_document.layer(&path).unwrap().as_text().unwrap();
|
||||
responses.push_back(DocumentOperation::SetTextEditability { path, editable }.into());
|
||||
if editable {
|
||||
let color = if let Fill::Solid(solid_color) = text.style.fill() { *solid_color } else { Color::BLACK };
|
||||
let color = if let Fill::Solid(solid_color) = text.path_style.fill() { *solid_color } else { Color::BLACK };
|
||||
responses.push_back(
|
||||
FrontendMessage::DisplayEditableTextbox {
|
||||
text: text.text.clone(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use graphene::document::FontCache;
|
||||
use graphene::layers::blend_mode::BlendMode;
|
||||
use graphene::layers::layer_info::{Layer, LayerData, LayerDataType};
|
||||
use graphene::layers::style::ViewMode;
|
||||
@@ -20,14 +21,14 @@ impl LayerMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layer_panel_entry(layer_metadata: &LayerMetadata, transform: DAffine2, layer: &Layer, path: Vec<LayerId>) -> LayerPanelEntry {
|
||||
pub fn layer_panel_entry(layer_metadata: &LayerMetadata, transform: DAffine2, layer: &Layer, path: Vec<LayerId>, font_cache: &FontCache) -> LayerPanelEntry {
|
||||
let name = layer.name.clone().unwrap_or_else(|| String::from(""));
|
||||
let arr = layer.data.bounding_box(transform).unwrap_or([DVec2::ZERO, DVec2::ZERO]);
|
||||
let arr = layer.data.bounding_box(transform, font_cache).unwrap_or([DVec2::ZERO, DVec2::ZERO]);
|
||||
let arr = arr.iter().map(|x| (*x).into()).collect::<Vec<(f64, f64)>>();
|
||||
|
||||
let mut thumbnail = String::new();
|
||||
let mut svg_defs = String::new();
|
||||
layer.data.clone().render(&mut thumbnail, &mut svg_defs, &mut vec![transform], ViewMode::Normal);
|
||||
layer.data.clone().render(&mut thumbnail, &mut svg_defs, &mut vec![transform], ViewMode::Normal, font_cache);
|
||||
let transform = transform.to_cols_array().iter().map(ToString::to_string).collect::<Vec<_>>().join(",");
|
||||
let thumbnail = if let [(x_min, y_min), (x_max, y_max)] = arr.as_slice() {
|
||||
format!(
|
||||
|
||||
@@ -52,7 +52,7 @@ impl PortfolioMessageHandler {
|
||||
name
|
||||
}
|
||||
|
||||
// TODO Fix how this doesn't preserve tab order upon loading new document from file>load
|
||||
// TODO Fix how this doesn't preserve tab order upon loading new document from *File > Load*
|
||||
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: u64, replace_first_empty: bool, responses: &mut VecDeque<Message>) {
|
||||
// Special case when loading a document on an empty page
|
||||
if replace_first_empty && self.active_document().is_unmodified_default() {
|
||||
@@ -79,6 +79,7 @@ impl PortfolioMessageHandler {
|
||||
);
|
||||
|
||||
new_document.load_image_data(responses, &new_document.graphene_document.root.data, Vec::new());
|
||||
new_document.load_default_font(responses);
|
||||
|
||||
self.documents.insert(document_id, new_document);
|
||||
|
||||
|
||||
@@ -9,15 +9,40 @@ use serde::{Deserialize, Serialize};
|
||||
#[impl_message(Message, DocumentMessage, PropertiesPanel)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum PropertiesPanelMessage {
|
||||
CheckSelectedWasDeleted { path: Vec<LayerId> },
|
||||
CheckSelectedWasUpdated { path: Vec<LayerId> },
|
||||
CheckSelectedWasDeleted {
|
||||
path: Vec<LayerId>,
|
||||
},
|
||||
CheckSelectedWasUpdated {
|
||||
path: Vec<LayerId>,
|
||||
},
|
||||
ClearSelection,
|
||||
ModifyFill { fill: Fill },
|
||||
ModifyName { name: String },
|
||||
ModifyStroke { stroke: Stroke },
|
||||
ModifyTransform { value: f64, transform_op: TransformOp },
|
||||
ModifyFill {
|
||||
fill: Fill,
|
||||
},
|
||||
ModifyFont {
|
||||
font_family: String,
|
||||
font_style: String,
|
||||
font_file: Option<String>,
|
||||
size: f64,
|
||||
},
|
||||
ModifyName {
|
||||
name: String,
|
||||
},
|
||||
ModifyStroke {
|
||||
stroke: Stroke,
|
||||
},
|
||||
ModifyText {
|
||||
new_text: String,
|
||||
},
|
||||
ModifyTransform {
|
||||
value: f64,
|
||||
transform_op: TransformOp,
|
||||
},
|
||||
ResendActiveProperties,
|
||||
SetActiveLayers { paths: Vec<Vec<LayerId>>, document: TargetDocument },
|
||||
SetActiveLayers {
|
||||
paths: Vec<Vec<LayerId>>,
|
||||
document: TargetDocument,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
|
||||
@@ -3,15 +3,16 @@ use super::utility_types::TargetDocument;
|
||||
use crate::document::properties_panel_message::TransformOp;
|
||||
use crate::layout::layout_message::LayoutTarget;
|
||||
use crate::layout::widgets::{
|
||||
ColorInput, IconLabel, LayoutRow, NumberInput, PopoverButton, RadioEntryData, RadioInput, Separator, SeparatorDirection, SeparatorType, TextInput, TextLabel, Widget, WidgetCallback, WidgetHolder,
|
||||
WidgetLayout,
|
||||
ColorInput, FontInput, IconLabel, LayoutRow, NumberInput, PopoverButton, RadioEntryData, RadioInput, Separator, SeparatorDirection, SeparatorType, TextAreaInput, TextInput, TextLabel, Widget,
|
||||
WidgetCallback, WidgetHolder, WidgetLayout,
|
||||
};
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::color::Color;
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::document::{Document as GrapheneDocument, FontCache};
|
||||
use graphene::layers::layer_info::{Layer, LayerDataType};
|
||||
use graphene::layers::style::{Fill, LineCap, LineJoin, Stroke};
|
||||
use graphene::layers::text_layer::TextLayer;
|
||||
use graphene::{LayerId, Operation};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -148,6 +149,23 @@ impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerDat
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
ModifyFont {
|
||||
font_family,
|
||||
font_style,
|
||||
font_file,
|
||||
size,
|
||||
} => {
|
||||
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
||||
|
||||
responses.push_back(self.create_document_operation(Operation::ModifyFont {
|
||||
path,
|
||||
font_family,
|
||||
font_style,
|
||||
font_file,
|
||||
size,
|
||||
}));
|
||||
responses.push_back(ResendActiveProperties.into());
|
||||
}
|
||||
ModifyTransform { value, transform_op } => {
|
||||
let (path, target_document) = self.active_selection.as_ref().expect("Received update for properties panel with no active layer");
|
||||
let layer = get_document(*target_document).layer(path).unwrap();
|
||||
@@ -162,8 +180,8 @@ impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerDat
|
||||
};
|
||||
|
||||
let scale = match transform_op {
|
||||
Width => layer.bounding_transform().scale_x() / layer.transform.scale_x(),
|
||||
Height => layer.bounding_transform().scale_y() / layer.transform.scale_y(),
|
||||
Width => layer.bounding_transform(&get_document(*target_document).font_cache).scale_x() / layer.transform.scale_x(),
|
||||
Height => layer.bounding_transform(&get_document(*target_document).font_cache).scale_y() / layer.transform.scale_y(),
|
||||
_ => 1.,
|
||||
};
|
||||
|
||||
@@ -184,6 +202,10 @@ impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerDat
|
||||
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
||||
responses.push_back(self.create_document_operation(Operation::SetLayerStroke { path, stroke }))
|
||||
}
|
||||
ModifyText { new_text } => {
|
||||
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
||||
responses.push_back(Operation::SetTextContent { path, new_text }.into())
|
||||
}
|
||||
CheckSelectedWasUpdated { path } => {
|
||||
if self.matches_selected(&path) {
|
||||
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into())
|
||||
@@ -212,8 +234,8 @@ impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerDat
|
||||
let (path, target_document) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
||||
let layer = get_document(target_document).layer(&path).unwrap();
|
||||
match target_document {
|
||||
TargetDocument::Artboard => register_artboard_layer_properties(layer, responses),
|
||||
TargetDocument::Artwork => register_artwork_layer_properties(layer, responses),
|
||||
TargetDocument::Artboard => register_artboard_layer_properties(layer, responses, &get_document(target_document).font_cache),
|
||||
TargetDocument::Artwork => register_artwork_layer_properties(layer, responses, &get_document(target_document).font_cache),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,7 +246,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerDat
|
||||
}
|
||||
}
|
||||
|
||||
fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>) {
|
||||
fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>, font_cache: &FontCache) {
|
||||
let options_bar = vec![LayoutRow::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::IconLabel(IconLabel {
|
||||
@@ -326,7 +348,7 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: layer.bounding_transform().scale_x(),
|
||||
value: layer.bounding_transform(font_cache).scale_x(),
|
||||
label: "W".into(),
|
||||
unit: " px".into(),
|
||||
on_update: WidgetCallback::new(|number_input: &NumberInput| {
|
||||
@@ -343,7 +365,7 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: layer.bounding_transform().scale_y(),
|
||||
value: layer.bounding_transform(font_cache).scale_y(),
|
||||
label: "H".into(),
|
||||
unit: " px".into(),
|
||||
on_update: WidgetCallback::new(|number_input: &NumberInput| {
|
||||
@@ -405,7 +427,7 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
|
||||
);
|
||||
}
|
||||
|
||||
fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>) {
|
||||
fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>, font_cache: &FontCache) {
|
||||
let options_bar = vec![LayoutRow::Row {
|
||||
widgets: vec![
|
||||
match &layer.data {
|
||||
@@ -456,20 +478,21 @@ fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque<Mes
|
||||
let properties_body = match &layer.data {
|
||||
LayerDataType::Shape(shape) => {
|
||||
if let Some(fill_layout) = node_section_fill(shape.style.fill()) {
|
||||
vec![node_section_transform(layer), fill_layout, node_section_stroke(&shape.style.stroke().unwrap_or_default())]
|
||||
vec![node_section_transform(layer, font_cache), fill_layout, node_section_stroke(&shape.style.stroke().unwrap_or_default())]
|
||||
} else {
|
||||
vec![node_section_transform(layer), node_section_stroke(&shape.style.stroke().unwrap_or_default())]
|
||||
vec![node_section_transform(layer, font_cache), node_section_stroke(&shape.style.stroke().unwrap_or_default())]
|
||||
}
|
||||
}
|
||||
LayerDataType::Text(text) => {
|
||||
vec![
|
||||
node_section_transform(layer),
|
||||
node_section_fill(text.style.fill()).expect("Text should have fill"),
|
||||
node_section_stroke(&text.style.stroke().unwrap_or_default()),
|
||||
node_section_transform(layer, font_cache),
|
||||
node_section_font(text),
|
||||
node_section_fill(text.path_style.fill()).expect("Text should have fill"),
|
||||
node_section_stroke(&text.path_style.stroke().unwrap_or_default()),
|
||||
]
|
||||
}
|
||||
LayerDataType::Image(_) => {
|
||||
vec![node_section_transform(layer)]
|
||||
vec![node_section_transform(layer, font_cache)]
|
||||
}
|
||||
_ => {
|
||||
vec![]
|
||||
@@ -492,7 +515,7 @@ fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque<Mes
|
||||
);
|
||||
}
|
||||
|
||||
fn node_section_transform(layer: &Layer) -> LayoutRow {
|
||||
fn node_section_transform(layer: &Layer, font_cache: &FontCache) -> LayoutRow {
|
||||
LayoutRow::Section {
|
||||
name: "Transform".into(),
|
||||
layout: vec![
|
||||
@@ -616,7 +639,7 @@ fn node_section_transform(layer: &Layer) -> LayoutRow {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: layer.bounding_transform().scale_x(),
|
||||
value: layer.bounding_transform(font_cache).scale_x(),
|
||||
label: "W".into(),
|
||||
unit: " px".into(),
|
||||
on_update: WidgetCallback::new(|number_input: &NumberInput| {
|
||||
@@ -633,7 +656,7 @@ fn node_section_transform(layer: &Layer) -> LayoutRow {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: layer.bounding_transform().scale_y(),
|
||||
value: layer.bounding_transform(font_cache).scale_y(),
|
||||
label: "H".into(),
|
||||
unit: " px".into(),
|
||||
on_update: WidgetCallback::new(|number_input: &NumberInput| {
|
||||
@@ -651,6 +674,115 @@ fn node_section_transform(layer: &Layer) -> LayoutRow {
|
||||
}
|
||||
}
|
||||
|
||||
fn node_section_font(layer: &TextLayer) -> LayoutRow {
|
||||
let font_family = layer.font_family.clone();
|
||||
let font_style = layer.font_style.clone();
|
||||
let font_file = layer.font_file.clone();
|
||||
let size = layer.size;
|
||||
LayoutRow::Section {
|
||||
name: "Font".into(),
|
||||
layout: vec![
|
||||
LayoutRow::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Text".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextAreaInput(TextAreaInput {
|
||||
value: layer.text.clone(),
|
||||
on_update: WidgetCallback::new(|text_area: &TextAreaInput| PropertiesPanelMessage::ModifyText { new_text: text_area.value.clone() }.into()),
|
||||
})),
|
||||
],
|
||||
},
|
||||
LayoutRow::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Font".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::FontInput(FontInput {
|
||||
is_style_picker: false,
|
||||
font_family: layer.font_family.clone(),
|
||||
font_style: layer.font_style.clone(),
|
||||
font_file: String::new(),
|
||||
on_update: WidgetCallback::new(move |font_input: &FontInput| {
|
||||
PropertiesPanelMessage::ModifyFont {
|
||||
font_family: font_input.font_family.clone(),
|
||||
font_style: font_input.font_style.clone(),
|
||||
font_file: Some(font_input.font_file.clone()),
|
||||
size,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
})),
|
||||
],
|
||||
},
|
||||
LayoutRow::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Style".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::FontInput(FontInput {
|
||||
is_style_picker: true,
|
||||
font_family: layer.font_family.clone(),
|
||||
font_style: layer.font_style.clone(),
|
||||
font_file: String::new(),
|
||||
on_update: WidgetCallback::new(move |font_input: &FontInput| {
|
||||
PropertiesPanelMessage::ModifyFont {
|
||||
font_family: font_input.font_family.clone(),
|
||||
font_style: font_input.font_style.clone(),
|
||||
font_file: Some(font_input.font_file.clone()),
|
||||
size,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
})),
|
||||
],
|
||||
},
|
||||
LayoutRow::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Size".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: layer.size,
|
||||
min: Some(1.),
|
||||
unit: " px".into(),
|
||||
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
||||
PropertiesPanelMessage::ModifyFont {
|
||||
font_family: font_family.clone(),
|
||||
font_style: font_style.clone(),
|
||||
font_file: font_file.clone(),
|
||||
size: number_input.value,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
||||
match fill {
|
||||
Fill::Solid(_) | Fill::None => Some(LayoutRow::Section {
|
||||
|
||||
@@ -45,7 +45,7 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMeta
|
||||
selected.revert_operation();
|
||||
typing.clear();
|
||||
} else {
|
||||
*selected.pivot = selected.calculate_pivot();
|
||||
*selected.pivot = selected.calculate_pivot(&document.font_cache);
|
||||
}
|
||||
|
||||
*mouse_position = ipp.mouse.position;
|
||||
@@ -128,7 +128,7 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMeta
|
||||
self.transform_operation.apply_transform_operation(&mut selected, self.snap);
|
||||
}
|
||||
TransformOperation::Rotating(rotation) => {
|
||||
let selected_pivot = selected.calculate_pivot();
|
||||
let selected_pivot = selected.calculate_pivot(&document.font_cache);
|
||||
let angle = {
|
||||
let start_offset = self.mouse_position - selected_pivot;
|
||||
let end_offset = ipp.mouse.position - selected_pivot;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL};
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::document::Document;
|
||||
use graphene::document::{Document, FontCache};
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -210,7 +210,7 @@ impl<'a> Selected<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_pivot(&mut self) -> DVec2 {
|
||||
pub fn calculate_pivot(&mut self, font_cache: &FontCache) -> DVec2 {
|
||||
let xy_summation = self
|
||||
.selected
|
||||
.iter()
|
||||
@@ -221,7 +221,7 @@ impl<'a> Selected<'a> {
|
||||
.document
|
||||
.layer(path)
|
||||
.unwrap()
|
||||
.aabounding_box_for_transform(multiplied_transform)
|
||||
.aabounding_box_for_transform(multiplied_transform, font_cache)
|
||||
.unwrap_or([multiplied_transform.translation; 2]);
|
||||
|
||||
(bounds[0] + bounds[1]) / 2.
|
||||
|
||||
@@ -24,8 +24,10 @@ pub enum FrontendMessage {
|
||||
DisplayRemoveEditableTextbox,
|
||||
|
||||
// Trigger prefix: cause a browser API to do something
|
||||
TriggerDefaultFontLoad,
|
||||
TriggerFileDownload { document: String, name: String },
|
||||
TriggerFileUpload,
|
||||
TriggerFontLoad { font: String },
|
||||
TriggerIndexedDbRemoveDocument { document_id: u64 },
|
||||
TriggerIndexedDbWriteDocument { document: String, details: FrontendDocumentDetails, version: String },
|
||||
TriggerTextCommit,
|
||||
|
||||
@@ -91,12 +91,36 @@ impl MessageHandler<LayoutMessage, ()> for LayoutMessageHandler {
|
||||
let callback_message = (text_input.on_update.callback)(text_input);
|
||||
responses.push_back(callback_message);
|
||||
}
|
||||
Widget::TextAreaInput(text_area_input) => {
|
||||
let update_value = value.as_str().expect("TextAreaInput update was not of type: string");
|
||||
text_area_input.value = update_value.into();
|
||||
let callback_message = (text_area_input.on_update.callback)(text_area_input);
|
||||
responses.push_back(callback_message);
|
||||
}
|
||||
Widget::ColorInput(color_input) => {
|
||||
let update_value = value.as_str().map(String::from);
|
||||
color_input.value = update_value;
|
||||
let callback_message = (color_input.on_update.callback)(color_input);
|
||||
responses.push_back(callback_message);
|
||||
}
|
||||
Widget::FontInput(font_input) => {
|
||||
let update_value = value.as_object().expect("FontInput update was not of type: object");
|
||||
let font_family_value = update_value.get("fontFamily").expect("FontInput update does not have a fontFamily");
|
||||
let font_style_value = update_value.get("fontStyle").expect("FontInput update does not have a fontStyle");
|
||||
let font_file_value = update_value.get("fontFile").expect("FontInput update does not have a fontFile");
|
||||
|
||||
let font_family = font_family_value.as_str().expect("FontInput update fontFamily was not of type: string");
|
||||
let font_style = font_style_value.as_str().expect("FontInput update fontStyle was not of type: string");
|
||||
let font_file = font_file_value.as_str().expect("FontInput update fontFile was not of type: string");
|
||||
|
||||
font_input.font_family = font_family.into();
|
||||
font_input.font_style = font_style.into();
|
||||
font_input.font_file = font_file.into();
|
||||
|
||||
responses.push_back(DocumentMessage::LoadFont { font: font_file.into() }.into());
|
||||
let callback_message = (font_input.on_update.callback)(font_input);
|
||||
responses.push_back(callback_message);
|
||||
}
|
||||
Widget::TextLabel(_) => {}
|
||||
};
|
||||
self.send_layout(layout_target, responses);
|
||||
|
||||
@@ -151,6 +151,7 @@ impl<T> Default for WidgetCallback<T> {
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Widget {
|
||||
ColorInput(ColorInput),
|
||||
FontInput(FontInput),
|
||||
IconButton(IconButton),
|
||||
IconLabel(IconLabel),
|
||||
NumberInput(NumberInput),
|
||||
@@ -158,6 +159,7 @@ pub enum Widget {
|
||||
PopoverButton(PopoverButton),
|
||||
RadioInput(RadioInput),
|
||||
Separator(Separator),
|
||||
TextAreaInput(TextAreaInput),
|
||||
TextInput(TextInput),
|
||||
TextLabel(TextLabel),
|
||||
}
|
||||
@@ -200,6 +202,15 @@ pub struct TextInput {
|
||||
pub on_update: WidgetCallback<TextInput>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct TextAreaInput {
|
||||
pub value: String,
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<TextAreaInput>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct ColorInput {
|
||||
@@ -212,6 +223,22 @@ pub struct ColorInput {
|
||||
pub can_set_transparent: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct FontInput {
|
||||
#[serde(rename = "isStyle")]
|
||||
pub is_style_picker: bool,
|
||||
#[serde(rename = "fontFamily")]
|
||||
pub font_family: String,
|
||||
#[serde(rename = "fontStyle")]
|
||||
pub font_style: String,
|
||||
#[serde(rename = "fontFile")]
|
||||
pub font_file: String,
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<FontInput>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub enum NumberInputIncrementBehavior {
|
||||
Add,
|
||||
|
||||
@@ -81,7 +81,7 @@ impl Default for GradientToolFsmState {
|
||||
|
||||
/// Computes the transform from gradient space to layer space (where gradient space is 0..1 in layer space)
|
||||
fn gradient_space_transform(path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler) -> DAffine2 {
|
||||
let bounds = layer.aabounding_box_for_transform(DAffine2::IDENTITY).unwrap();
|
||||
let bounds = layer.aabounding_box_for_transform(DAffine2::IDENTITY, &document.graphene_document.font_cache).unwrap();
|
||||
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
|
||||
|
||||
let multiplied = document.graphene_document.multiply_transforms(path).unwrap();
|
||||
|
||||
@@ -407,7 +407,7 @@ impl Fsm for SelectToolFsmState {
|
||||
let selected = selected.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.pivot, &selected, responses, &document.graphene_document);
|
||||
|
||||
*selected.pivot = selected.calculate_pivot();
|
||||
*selected.pivot = selected.calculate_pivot(&document.graphene_document.font_cache);
|
||||
}
|
||||
|
||||
data.layers_dragging = selected;
|
||||
|
||||
@@ -3,12 +3,14 @@ use crate::document::DocumentMessageHandler;
|
||||
use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::layout::layout_message::LayoutTarget;
|
||||
use crate::layout::widgets::{FontInput, LayoutRow, NumberInput, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene::document::FontCache;
|
||||
use graphene::intersection::Quad;
|
||||
use graphene::layers::style::{self, Fill, Stroke};
|
||||
use graphene::Operation;
|
||||
@@ -24,11 +26,19 @@ pub struct TextTool {
|
||||
|
||||
pub struct TextOptions {
|
||||
font_size: u32,
|
||||
font_name: String,
|
||||
font_style: String,
|
||||
font_file: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for TextOptions {
|
||||
fn default() -> Self {
|
||||
Self { font_size: 14 }
|
||||
Self {
|
||||
font_size: 24,
|
||||
font_name: "Merriweather".into(),
|
||||
font_style: "Normal (400)".into(),
|
||||
font_file: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,21 +68,60 @@ pub enum TextMessage {
|
||||
#[remain::sorted]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum TextOptionsUpdate {
|
||||
Font { family: String, style: String, file: String },
|
||||
FontSize(u32),
|
||||
}
|
||||
|
||||
impl PropertyHolder for TextTool {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
WidgetLayout::new(vec![LayoutRow::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
unit: " px".into(),
|
||||
label: "Font Size".into(),
|
||||
value: self.options.font_size as f64,
|
||||
is_integer: true,
|
||||
min: Some(1.),
|
||||
on_update: WidgetCallback::new(|number_input: &NumberInput| TextMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value as u32)).into()),
|
||||
..NumberInput::default()
|
||||
}))],
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::FontInput(FontInput {
|
||||
is_style_picker: false,
|
||||
font_family: self.options.font_name.clone(),
|
||||
font_style: self.options.font_style.clone(),
|
||||
on_update: WidgetCallback::new(|font_input: &FontInput| {
|
||||
TextMessage::UpdateOptions(TextOptionsUpdate::Font {
|
||||
family: font_input.font_family.clone(),
|
||||
style: font_input.font_style.clone(),
|
||||
file: font_input.font_file.clone(),
|
||||
})
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
separator_type: SeparatorType::Related,
|
||||
})),
|
||||
WidgetHolder::new(Widget::FontInput(FontInput {
|
||||
is_style_picker: true,
|
||||
font_family: self.options.font_name.clone(),
|
||||
font_style: self.options.font_style.clone(),
|
||||
on_update: WidgetCallback::new(|font_input: &FontInput| {
|
||||
TextMessage::UpdateOptions(TextOptionsUpdate::Font {
|
||||
family: font_input.font_family.clone(),
|
||||
style: font_input.font_style.clone(),
|
||||
file: font_input.font_file.clone(),
|
||||
})
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
separator_type: SeparatorType::Related,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
unit: " px".into(),
|
||||
label: "Size".into(),
|
||||
value: self.options.font_size as f64,
|
||||
is_integer: true,
|
||||
min: Some(1.),
|
||||
on_update: WidgetCallback::new(|number_input: &NumberInput| TextMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value as u32)).into()),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
],
|
||||
}])
|
||||
}
|
||||
}
|
||||
@@ -91,6 +140,13 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
|
||||
|
||||
if let ToolMessage::Text(TextMessage::UpdateOptions(action)) = action {
|
||||
match action {
|
||||
TextOptionsUpdate::Font { family, style, file } => {
|
||||
self.options.font_name = family;
|
||||
self.options.font_style = style;
|
||||
self.options.font_file = Some(file);
|
||||
|
||||
self.register_properties(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
TextOptionsUpdate::FontSize(font_size) => self.options.font_size = font_size,
|
||||
}
|
||||
return;
|
||||
@@ -155,25 +211,33 @@ fn resize_overlays(overlays: &mut Vec<Vec<LayerId>>, responses: &mut VecDeque<Me
|
||||
}
|
||||
}
|
||||
|
||||
fn update_overlays(document: &DocumentMessageHandler, data: &mut TextToolData, responses: &mut VecDeque<Message>) {
|
||||
fn update_overlays(document: &DocumentMessageHandler, data: &mut TextToolData, responses: &mut VecDeque<Message>, font_cache: &FontCache) {
|
||||
let visible_text_layers = document.selected_visible_text_layers().collect::<Vec<_>>();
|
||||
|
||||
resize_overlays(&mut data.overlays, responses, visible_text_layers.len());
|
||||
|
||||
for (layer_path, overlay_path) in visible_text_layers.into_iter().zip(&data.overlays) {
|
||||
let bounds = document
|
||||
.graphene_document
|
||||
.layer(layer_path)
|
||||
.unwrap()
|
||||
.aabounding_box_for_transform(document.graphene_document.multiply_transforms(layer_path).unwrap())
|
||||
.unwrap();
|
||||
let bounds = visible_text_layers
|
||||
.into_iter()
|
||||
.zip(&data.overlays)
|
||||
.filter_map(|(layer_path, overlay_path)| {
|
||||
document
|
||||
.graphene_document
|
||||
.layer(layer_path)
|
||||
.unwrap()
|
||||
.aabounding_box_for_transform(document.graphene_document.multiply_transforms(layer_path).unwrap(), font_cache)
|
||||
.map(|bounds| (bounds, overlay_path))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let new_len = bounds.len();
|
||||
|
||||
for (bounds, overlay_path) in bounds {
|
||||
let operation = Operation::SetLayerTransformInViewport {
|
||||
path: overlay_path.to_vec(),
|
||||
transform: transform_from_box(bounds[0], bounds[1]),
|
||||
};
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
}
|
||||
resize_overlays(&mut data.overlays, responses, new_len);
|
||||
}
|
||||
|
||||
impl Fsm for TextToolFsmState {
|
||||
@@ -196,7 +260,7 @@ impl Fsm for TextToolFsmState {
|
||||
if let ToolMessage::Text(event) = event {
|
||||
match (self, event) {
|
||||
(state, DocumentIsDirty) => {
|
||||
update_overlays(document, data, responses);
|
||||
update_overlays(document, data, responses, &document.graphene_document.font_cache);
|
||||
|
||||
state
|
||||
}
|
||||
@@ -244,6 +308,9 @@ impl Fsm for TextToolFsmState {
|
||||
else if state == TextToolFsmState::Ready {
|
||||
let transform = DAffine2::from_translation(input.mouse.position).to_cols_array();
|
||||
let font_size = tool_options.font_size;
|
||||
let font_name = tool_options.font_name.clone();
|
||||
let font_style = tool_options.font_style.clone();
|
||||
let font_file = tool_options.font_file.clone();
|
||||
data.path = document.get_path_for_new_layer();
|
||||
|
||||
responses.push_back(
|
||||
@@ -254,6 +321,9 @@ impl Fsm for TextToolFsmState {
|
||||
text: r#""#.to_string(),
|
||||
style: style::PathStyle::new(None, Fill::solid(tool_data.primary_color)),
|
||||
size: font_size as f64,
|
||||
font_name,
|
||||
font_style,
|
||||
font_file,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
@@ -329,7 +399,8 @@ impl Fsm for TextToolFsmState {
|
||||
}
|
||||
(Editing, UpdateBounds { new_text }) => {
|
||||
resize_overlays(&mut data.overlays, responses, 1);
|
||||
let mut path = document.graphene_document.layer(&data.path).unwrap().as_text().unwrap().bounding_box(&new_text).to_path(0.1);
|
||||
let text = document.graphene_document.layer(&data.path).unwrap().as_text().unwrap();
|
||||
let mut path = text.bounding_box(&new_text, text.load_face(&document.graphene_document.font_cache)).to_path(0.1);
|
||||
|
||||
fn glam_to_kurbo(transform: DAffine2) -> kurbo::Affine {
|
||||
kurbo::Affine::new(transform.to_cols_array())
|
||||
|
||||
Reference in New Issue
Block a user