mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-25 16:58:12 +08:00
Make font selection show a live preview on hover; move its code to the backend (#3487)
* Remove FontInput.svelte * Move font picking to the backend * Fix Text tool font choice style turning to "-" on font that doesn't support previous style
This commit is contained in:
@@ -44,7 +44,6 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
||||
preferences,
|
||||
viewport,
|
||||
} = context;
|
||||
let font_cache = &persistent_data.font_cache;
|
||||
|
||||
match message {
|
||||
// Messages
|
||||
@@ -122,11 +121,11 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
||||
document_id,
|
||||
global_tool_data: &self.tool_state.document_tool_data,
|
||||
input,
|
||||
font_cache,
|
||||
shape_editor: &mut self.shape_editor,
|
||||
node_graph,
|
||||
preferences,
|
||||
viewport,
|
||||
persistent_data,
|
||||
};
|
||||
|
||||
if let Some(tool_abort_message) = tool.event_to_message_map().tool_abort {
|
||||
@@ -217,7 +216,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().send_layout(responses, LayoutTarget::ToolOptions);
|
||||
tool_data.tools.get(active_tool).unwrap().refresh_options(responses, persistent_data);
|
||||
|
||||
// Notify the frontend about the initial active tool
|
||||
tool_data.send_layout(responses, LayoutTarget::ToolShelf, preferences.brush_tool);
|
||||
@@ -230,11 +229,11 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
||||
document_id,
|
||||
global_tool_data: &self.tool_state.document_tool_data,
|
||||
input,
|
||||
font_cache,
|
||||
shape_editor: &mut self.shape_editor,
|
||||
node_graph,
|
||||
preferences,
|
||||
viewport,
|
||||
persistent_data,
|
||||
};
|
||||
|
||||
// Set initial hints and cursor
|
||||
@@ -257,7 +256,8 @@ 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().send_layout(responses, LayoutTarget::ToolOptions);
|
||||
|
||||
tool_data.tools.get(&tool_data.active_tool_type).unwrap().refresh_options(responses, persistent_data);
|
||||
}
|
||||
ToolMessage::RefreshToolShelf => {
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
@@ -341,11 +341,11 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
||||
document_id,
|
||||
global_tool_data: &self.tool_state.document_tool_data,
|
||||
input,
|
||||
font_cache,
|
||||
shape_editor: &mut self.shape_editor,
|
||||
node_graph,
|
||||
preferences,
|
||||
viewport,
|
||||
persistent_data,
|
||||
};
|
||||
if matches!(tool_message, ToolMessage::UpdateHints) {
|
||||
if graph_view_overlay_open {
|
||||
|
||||
@@ -600,7 +600,7 @@ impl Fsm for SelectToolFsmState {
|
||||
document,
|
||||
input,
|
||||
viewport,
|
||||
font_cache,
|
||||
persistent_data,
|
||||
..
|
||||
} = tool_action_data;
|
||||
|
||||
@@ -625,7 +625,7 @@ impl Fsm for SelectToolFsmState {
|
||||
overlay_context.outline(document.metadata().layer_with_free_points_outline(layer), layer_to_viewport, None);
|
||||
|
||||
if is_layer_fed_by_node_of_name(layer, &document.network_interface, "Text") {
|
||||
let transformed_quad = layer_to_viewport * text_bounding_box(layer, document, font_cache);
|
||||
let transformed_quad = layer_to_viewport * text_bounding_box(layer, document, &persistent_data.font_cache);
|
||||
overlay_context.dashed_quad(transformed_quad, None, None, Some(7.), Some(5.), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +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::{FontCatalog, FontCatalogStyle, PersistentData};
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name};
|
||||
@@ -13,6 +14,7 @@ use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData};
|
||||
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::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Color;
|
||||
@@ -31,8 +33,7 @@ pub struct TextOptions {
|
||||
font_size: f64,
|
||||
line_height_ratio: f64,
|
||||
character_spacing: f64,
|
||||
font_name: String,
|
||||
font_style: String,
|
||||
font: Font,
|
||||
fill: ToolColorOptions,
|
||||
tilt: f64,
|
||||
align: TextAlign,
|
||||
@@ -44,8 +45,7 @@ impl Default for TextOptions {
|
||||
font_size: 24.,
|
||||
line_height_ratio: 1.2,
|
||||
character_spacing: 0.,
|
||||
font_name: graphene_std::consts::DEFAULT_FONT_FAMILY.into(),
|
||||
font_style: graphene_std::consts::DEFAULT_FONT_STYLE.into(),
|
||||
font: Font::new(graphene_std::consts::DEFAULT_FONT_FAMILY.into(), graphene_std::consts::DEFAULT_FONT_STYLE.into()),
|
||||
fill: ToolColorOptions::new_primary(),
|
||||
tilt: 0.,
|
||||
align: TextAlign::default(),
|
||||
@@ -71,13 +71,14 @@ pub enum TextToolMessage {
|
||||
TextChange { new_text: String, is_left_or_right_click: bool },
|
||||
UpdateBounds { new_text: String },
|
||||
UpdateOptions { options: TextOptionsUpdate },
|
||||
RefreshEditingFontData,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum TextOptionsUpdate {
|
||||
FillColor(Option<Color>),
|
||||
FillColorType(ToolColorType),
|
||||
Font { family: String, style: String },
|
||||
Font { font: Font },
|
||||
FontSize(f64),
|
||||
LineHeightRatio(f64),
|
||||
Align(TextAlign),
|
||||
@@ -96,31 +97,84 @@ impl ToolMetadata for TextTool {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_text_widgets(tool: &TextTool) -> Vec<WidgetInstance> {
|
||||
let font = FontInput::new(&tool.options.font_name, &tool.options.font_style)
|
||||
.is_style_picker(false)
|
||||
.on_update(|font_input: &FontInput| {
|
||||
fn create_text_widgets(tool: &TextTool, font_catalog: &FontCatalog) -> Vec<WidgetInstance> {
|
||||
fn update_options(font: Font, commit_style: Option<String>) -> impl Fn(&()) -> Message + Clone {
|
||||
let mut font = font;
|
||||
if let Some(style) = commit_style {
|
||||
font.font_style = style;
|
||||
}
|
||||
|
||||
move |_| {
|
||||
TextToolMessage::UpdateOptions {
|
||||
options: TextOptionsUpdate::Font {
|
||||
family: font_input.font_family.clone(),
|
||||
style: font_input.font_style.clone(),
|
||||
},
|
||||
options: TextOptionsUpdate::Font { font: font.clone() },
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_instance();
|
||||
let style = FontInput::new(&tool.options.font_name, &tool.options.font_style)
|
||||
.is_style_picker(true)
|
||||
.on_update(|font_input: &FontInput| {
|
||||
TextToolMessage::UpdateOptions {
|
||||
options: TextOptionsUpdate::Font {
|
||||
family: font_input.font_family.clone(),
|
||||
style: font_input.font_style.clone(),
|
||||
},
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_instance();
|
||||
}
|
||||
}
|
||||
|
||||
let font = DropdownInput::new(vec![
|
||||
font_catalog
|
||||
.0
|
||||
.iter()
|
||||
.map(|family| {
|
||||
let font = Font::new(family.name.clone(), tool.options.font.font_style.clone());
|
||||
let commit_style = font_catalog.find_font_style_in_catalog(&tool.options.font).map(|style| style.to_named_style());
|
||||
let update = update_options(font.clone(), None);
|
||||
let commit = update_options(font, commit_style);
|
||||
|
||||
MenuListEntry::new(family.name.clone())
|
||||
.label(family.name.clone())
|
||||
.font(family.closest_style(400, false).preview_url(&family.name))
|
||||
.on_update(update)
|
||||
.on_commit(commit)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
])
|
||||
.selected_index(font_catalog.0.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| {
|
||||
let build_entry = |style: &FontCatalogStyle| {
|
||||
let font_style = style.to_named_style();
|
||||
|
||||
let font = Font::new(tool.options.font.font_family.clone(), font_style.clone());
|
||||
let commit_style = font_catalog.find_font_style_in_catalog(&tool.options.font).map(|style| style.to_named_style());
|
||||
let update = update_options(font.clone(), None);
|
||||
let commit = update_options(font, commit_style);
|
||||
|
||||
MenuListEntry::new(font_style.clone()).on_update(update).on_commit(commit).label(font_style)
|
||||
};
|
||||
|
||||
vec![
|
||||
family.styles.iter().filter(|style| !style.italic).map(build_entry).collect::<Vec<_>>(),
|
||||
family.styles.iter().filter(|style| style.italic).map(build_entry).collect::<Vec<_>>(),
|
||||
]
|
||||
})
|
||||
.filter(|styles| !styles.is_empty())
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.selected_index(
|
||||
font_catalog
|
||||
.0
|
||||
.iter()
|
||||
.find(|family| family.name == tool.options.font.font_family)
|
||||
.and_then(|family| {
|
||||
let not_italic = family.styles.iter().filter(|style| !style.italic);
|
||||
let italic = family.styles.iter().filter(|style| style.italic);
|
||||
not_italic
|
||||
.chain(italic)
|
||||
.position(|style| Some(style) == font_catalog.find_font_style_in_catalog(&tool.options.font).as_ref())
|
||||
})
|
||||
.map(|i| i as u32),
|
||||
)
|
||||
.widget_instance();
|
||||
|
||||
let size = NumberInput::new(Some(tool.options.font_size))
|
||||
.unit(" px")
|
||||
.label("Size")
|
||||
@@ -172,9 +226,22 @@ fn create_text_widgets(tool: &TextTool) -> Vec<WidgetInstance> {
|
||||
]
|
||||
}
|
||||
|
||||
impl LayoutHolder for TextTool {
|
||||
fn layout(&self) -> Layout {
|
||||
let mut widgets = create_text_widgets(self);
|
||||
impl ToolRefreshOptions for TextTool {
|
||||
fn refresh_options(&self, responses: &mut VecDeque<Message>, persistent_data: &PersistentData) {
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions, &persistent_data.font_catalog);
|
||||
}
|
||||
}
|
||||
|
||||
impl TextTool {
|
||||
fn send_layout(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget, font_catalog: &FontCatalog) {
|
||||
responses.add(LayoutMessage::SendLayout {
|
||||
layout: self.layout(font_catalog),
|
||||
layout_target,
|
||||
});
|
||||
}
|
||||
|
||||
fn layout(&self, font_catalog: &FontCatalog) -> Layout {
|
||||
let mut widgets = create_text_widgets(self, font_catalog);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_instance());
|
||||
|
||||
@@ -215,11 +282,8 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Text
|
||||
return;
|
||||
};
|
||||
match options {
|
||||
TextOptionsUpdate::Font { family, style } => {
|
||||
self.options.font_name = family;
|
||||
self.options.font_style = style;
|
||||
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
TextOptionsUpdate::Font { font } => {
|
||||
self.options.font = font;
|
||||
}
|
||||
TextOptionsUpdate::FontSize(font_size) => self.options.font_size = font_size,
|
||||
TextOptionsUpdate::LineHeightRatio(line_height_ratio) => self.options.line_height_ratio = line_height_ratio,
|
||||
@@ -235,7 +299,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Text
|
||||
}
|
||||
}
|
||||
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions, &context.persistent_data.font_catalog);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
@@ -339,6 +403,7 @@ impl TextToolData {
|
||||
|
||||
TextToolFsmState::Ready
|
||||
}
|
||||
|
||||
/// Set the editing state of the currently modifying layer
|
||||
fn set_editing(&self, editable: bool, font_cache: &FontCache, responses: &mut VecDeque<Message>) {
|
||||
if let Some(editing_text) = self.editing_text.as_ref().filter(|_| editable) {
|
||||
@@ -347,7 +412,7 @@ impl TextToolData {
|
||||
line_height_ratio: editing_text.typesetting.line_height_ratio,
|
||||
font_size: editing_text.typesetting.font_size,
|
||||
color: editing_text.color.unwrap_or(Color::BLACK),
|
||||
url: font_cache.get_preview_url(&editing_text.font).cloned().unwrap_or_default(),
|
||||
font_data: font_cache.get(&editing_text.font).map(|(data, _)| data.clone()).unwrap_or_default(),
|
||||
transform: editing_text.transform.to_cols_array(),
|
||||
max_width: editing_text.typesetting.max_width,
|
||||
max_height: editing_text.typesetting.max_height,
|
||||
@@ -411,6 +476,7 @@ impl TextToolData {
|
||||
|
||||
self.layer = LayerNodeIdentifier::new_unchecked(NodeId::new());
|
||||
|
||||
responses.add(PortfolioMessage::LoadFontData { font: editing_text.font.clone() });
|
||||
responses.add(GraphOperationMessage::NewTextLayer {
|
||||
id: self.layer.to_node(),
|
||||
text: String::new(),
|
||||
@@ -498,10 +564,11 @@ impl Fsm for TextToolFsmState {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
font_cache,
|
||||
persistent_data,
|
||||
viewport,
|
||||
..
|
||||
} = transition_data;
|
||||
let font_cache = &persistent_data.font_cache;
|
||||
let fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
||||
.unwrap()
|
||||
.with_alpha(0.05)
|
||||
@@ -827,7 +894,7 @@ impl Fsm for TextToolFsmState {
|
||||
tilt: tool_options.tilt,
|
||||
align: tool_options.align,
|
||||
},
|
||||
font: Font::new(tool_options.font_name.clone(), tool_options.font_style.clone()),
|
||||
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);
|
||||
@@ -852,6 +919,14 @@ impl Fsm for TextToolFsmState {
|
||||
|
||||
TextToolFsmState::Ready
|
||||
}
|
||||
(TextToolFsmState::Editing, TextToolMessage::RefreshEditingFontData) => {
|
||||
let font = Font::new(tool_options.font.font_family.clone(), tool_options.font.font_style.clone());
|
||||
responses.add(FrontendMessage::DisplayEditableTextboxUpdateFontData {
|
||||
font_data: font_cache.get(&font).map(|(data, _)| data.clone()).unwrap_or_default(),
|
||||
});
|
||||
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
(TextToolFsmState::Editing, TextToolMessage::TextChange { new_text, is_left_or_right_click }) => {
|
||||
tool_data.new_text = new_text;
|
||||
|
||||
@@ -871,6 +946,7 @@ impl Fsm for TextToolFsmState {
|
||||
}
|
||||
|
||||
responses.add(FrontendMessage::TriggerTextCommit);
|
||||
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ 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::PersistentData;
|
||||
use crate::messages::preferences::PreferencesMessageHandler;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeType;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
use graphene_std::raster::color::Color;
|
||||
use graphene_std::text::FontCache;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{self, Debug};
|
||||
|
||||
@@ -24,18 +24,28 @@ pub struct ToolActionMessageContext<'a> {
|
||||
pub document_id: DocumentId,
|
||||
pub global_tool_data: &'a DocumentToolData,
|
||||
pub input: &'a InputPreprocessorMessageHandler,
|
||||
pub font_cache: &'a FontCache,
|
||||
pub persistent_data: &'a PersistentData,
|
||||
pub shape_editor: &'a mut ShapeState,
|
||||
pub node_graph: &'a NodeGraphExecutor,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
pub viewport: &'a ViewportMessageHandler,
|
||||
}
|
||||
|
||||
pub trait ToolCommon: for<'a, 'b> MessageHandler<ToolMessage, &'b mut ToolActionMessageContext<'a>> + LayoutHolder + ToolTransition + ToolMetadata {}
|
||||
impl<T> ToolCommon for T where T: for<'a, 'b> MessageHandler<ToolMessage, &'b mut ToolActionMessageContext<'a>> + LayoutHolder + ToolTransition + ToolMetadata {}
|
||||
pub trait ToolCommon: for<'a, 'b> MessageHandler<ToolMessage, &'b mut ToolActionMessageContext<'a>> + ToolRefreshOptions + ToolTransition + ToolMetadata {}
|
||||
impl<T> ToolCommon for T where T: for<'a, 'b> MessageHandler<ToolMessage, &'b mut ToolActionMessageContext<'a>> + ToolRefreshOptions + ToolTransition + ToolMetadata {}
|
||||
|
||||
type Tool = dyn ToolCommon + Send + Sync;
|
||||
|
||||
pub trait ToolRefreshOptions {
|
||||
fn refresh_options(&self, responses: &mut VecDeque<Message>, _persistent_data: &PersistentData);
|
||||
}
|
||||
|
||||
impl<T: LayoutHolder> ToolRefreshOptions for T {
|
||||
fn refresh_options(&self, responses: &mut VecDeque<Message>, _persistent_data: &PersistentData) {
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/// The FSM (finite state machine) is a flowchart between different operating states that a specific tool might be in.
|
||||
/// It is the central "core" logic area of each tool which is in charge of maintaining the state of the tool and responding to events coming from outside (like user input).
|
||||
/// For example, a tool might be `Ready` or `Drawing` depending on if the user is idle or actively drawing with the mouse held down.
|
||||
|
||||
Reference in New Issue
Block a user