Skip to content

Commit 2d6d054

Browse files
authored
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
1 parent 6f087eb commit 2d6d054

File tree

31 files changed

+765
-701
lines changed

31 files changed

+765
-701
lines changed

editor/src/dispatcher.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
5151
NodeGraphMessageDiscriminant::RunDocumentGraph,
5252
))),
5353
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::SubmitActiveGraphRender),
54-
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
54+
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontDataLoad),
5555
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateUIScale),
5656
];
5757
/// Since we don't need to update the frontend multiple times per frame,
@@ -69,6 +69,7 @@ const FRONTEND_UPDATE_MESSAGES: &[MessageDiscriminant] = &[
6969
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[
7070
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(EventMessageDiscriminant::AnimationFrame)),
7171
MessageDiscriminant::Animation(AnimationMessageDiscriminant::IncrementFrameCounter),
72+
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::AutoSaveAllDocuments),
7273
];
7374
// TODO: Find a way to combine these with the list above. We use strings for now since these are the standard variant names used by multiple messages. But having these also type-checked would be best.
7475
const DEBUG_MESSAGE_ENDING_BLOCK_LIST: &[&str] = &["PointerMove", "PointerOutsideViewport", "Overlays", "Draw", "CurrentTime", "Time"];
@@ -179,7 +180,7 @@ impl Dispatcher {
179180
}
180181
Message::Frontend(message) => {
181182
// Handle these messages immediately by returning early
182-
if let FrontendMessage::TriggerFontLoad { .. } = message {
183+
if let FrontendMessage::TriggerFontDataLoad { .. } | FrontendMessage::TriggerFontCatalogLoad = message {
183184
self.responses.push(message);
184185
self.cleanup_queues(false);
185186

@@ -359,8 +360,9 @@ impl Dispatcher {
359360
fn log_message(&self, message: &Message, queues: &[VecDeque<Message>], message_logging_verbosity: MessageLoggingVerbosity) {
360361
let discriminant = MessageDiscriminant::from(message);
361362
let is_blocked = DEBUG_MESSAGE_BLOCK_LIST.contains(&discriminant) || DEBUG_MESSAGE_ENDING_BLOCK_LIST.iter().any(|blocked_name| discriminant.local_name().ends_with(blocked_name));
363+
let is_empty_batched = if let Message::Batched { messages } = message { messages.is_empty() } else { false };
362364

363-
if !is_blocked {
365+
if !is_blocked && !is_empty_batched {
364366
match message_logging_verbosity {
365367
MessageLoggingVerbosity::Off => {}
366368
MessageLoggingVerbosity::Names => {

editor/src/messages/frontend/frontend_message.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,19 @@ pub enum FrontendMessage {
3939
#[serde(rename = "fontSize")]
4040
font_size: f64,
4141
color: Color,
42-
url: String,
42+
#[serde(rename = "fontData")]
43+
font_data: Vec<u8>,
4344
transform: [f64; 6],
4445
#[serde(rename = "maxWidth")]
4546
max_width: Option<f64>,
4647
#[serde(rename = "maxHeight")]
4748
max_height: Option<f64>,
4849
align: TextAlign,
4950
},
51+
DisplayEditableTextboxUpdateFontData {
52+
#[serde(rename = "fontData")]
53+
font_data: Vec<u8>,
54+
},
5055
DisplayEditableTextboxTransform {
5156
transform: [f64; 6],
5257
},
@@ -92,8 +97,10 @@ pub enum FrontendMessage {
9297
name: String,
9398
filename: String,
9499
},
95-
TriggerFontLoad {
100+
TriggerFontCatalogLoad,
101+
TriggerFontDataLoad {
96102
font: Font,
103+
url: String,
97104
},
98105
TriggerImport,
99106
TriggerPersistenceRemoveDocument {

editor/src/messages/layout/layout_message_handler.rs

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
22
use crate::messages::layout::utility_types::widget_prelude::*;
33
use crate::messages::prelude::*;
44
use graphene_std::raster::color::Color;
5-
use graphene_std::text::Font;
65
use graphene_std::vector::style::{FillChoice, GradientStops};
76
use serde_json::Value;
87
use std::collections::HashMap;
@@ -48,7 +47,6 @@ impl MessageHandler<LayoutMessage, LayoutMessageContext<'_>> for LayoutMessageHa
4847
}
4948
LayoutMessage::WidgetValueUpdate { layout_target, widget_id, value } => {
5049
self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Update, responses);
51-
responses.add(LayoutMessage::ResendActiveWidget { layout_target, widget_id });
5250
}
5351
}
5452
}
@@ -264,44 +262,6 @@ impl LayoutMessageHandler {
264262

265263
responses.add(callback_message);
266264
}
267-
Widget::FontInput(font_input) => {
268-
let callback_message = match action {
269-
WidgetValueAction::Commit => (font_input.on_commit.callback)(&()),
270-
WidgetValueAction::Update => {
271-
let Some(update_value) = value.as_object() else {
272-
error!("FontInput update was not of type: object");
273-
return;
274-
};
275-
let Some(font_family_value) = update_value.get("fontFamily") else {
276-
error!("FontInput update does not have a fontFamily");
277-
return;
278-
};
279-
let Some(font_style_value) = update_value.get("fontStyle") else {
280-
error!("FontInput update does not have a fontStyle");
281-
return;
282-
};
283-
284-
let Some(font_family) = font_family_value.as_str() else {
285-
error!("FontInput update fontFamily was not of type: string");
286-
return;
287-
};
288-
let Some(font_style) = font_style_value.as_str() else {
289-
error!("FontInput update fontStyle was not of type: string");
290-
return;
291-
};
292-
293-
font_input.font_family = font_family.into();
294-
font_input.font_style = font_style.into();
295-
296-
responses.add(PortfolioMessage::LoadFont {
297-
font: Font::new(font_family.into(), font_style.into()),
298-
});
299-
(font_input.on_update.callback)(font_input)
300-
}
301-
};
302-
303-
responses.add(callback_message);
304-
}
305265
Widget::IconButton(icon_button) => {
306266
let callback_message = match action {
307267
WidgetValueAction::Commit => (icon_button.on_commit.callback)(&()),

editor/src/messages/layout/utility_types/layout_widget.rs

Lines changed: 38 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -353,43 +353,7 @@ impl From<Vec<WidgetInstance>> for LayoutGroup {
353353
}
354354

355355
impl LayoutGroup {
356-
/// Applies a tooltip label to all widgets in this row or column without a tooltip.
357-
pub fn with_tooltip_label(self, label: impl Into<String>) -> Self {
358-
let (is_col, mut widgets) = match self {
359-
LayoutGroup::Column { widgets } => (true, widgets),
360-
LayoutGroup::Row { widgets } => (false, widgets),
361-
_ => unimplemented!(),
362-
};
363-
let label = label.into();
364-
for widget in &mut widgets {
365-
let val = match &mut widget.widget {
366-
Widget::CheckboxInput(x) => &mut x.tooltip_label,
367-
Widget::ColorInput(x) => &mut x.tooltip_label,
368-
Widget::CurveInput(x) => &mut x.tooltip_label,
369-
Widget::DropdownInput(x) => &mut x.tooltip_label,
370-
Widget::FontInput(x) => &mut x.tooltip_label,
371-
Widget::IconButton(x) => &mut x.tooltip_label,
372-
Widget::IconLabel(x) => &mut x.tooltip_label,
373-
Widget::ImageButton(x) => &mut x.tooltip_label,
374-
Widget::ImageLabel(x) => &mut x.tooltip_label,
375-
Widget::NumberInput(x) => &mut x.tooltip_label,
376-
Widget::ParameterExposeButton(x) => &mut x.tooltip_label,
377-
Widget::PopoverButton(x) => &mut x.tooltip_label,
378-
Widget::TextAreaInput(x) => &mut x.tooltip_label,
379-
Widget::TextButton(x) => &mut x.tooltip_label,
380-
Widget::TextInput(x) => &mut x.tooltip_label,
381-
Widget::TextLabel(x) => &mut x.tooltip_label,
382-
Widget::BreadcrumbTrailButtons(x) => &mut x.tooltip_label,
383-
Widget::ReferencePointInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::ShortcutLabel(_) | Widget::WorkingColorsInput(_) | Widget::NodeCatalog(_) => continue,
384-
};
385-
if val.is_empty() {
386-
val.clone_from(&label);
387-
}
388-
}
389-
if is_col { Self::Column { widgets } } else { Self::Row { widgets } }
390-
}
391-
392-
/// Applies a tooltip description to all widgets in this row or column without a tooltip.
356+
/// Applies a tooltip description to all widgets without a tooltip in this row or column.
393357
pub fn with_tooltip_description(self, description: impl Into<String>) -> Self {
394358
let (is_col, mut widgets) = match self {
395359
LayoutGroup::Column { widgets } => (true, widgets),
@@ -403,20 +367,24 @@ impl LayoutGroup {
403367
Widget::ColorInput(x) => &mut x.tooltip_description,
404368
Widget::CurveInput(x) => &mut x.tooltip_description,
405369
Widget::DropdownInput(x) => &mut x.tooltip_description,
406-
Widget::FontInput(x) => &mut x.tooltip_description,
407370
Widget::IconButton(x) => &mut x.tooltip_description,
408371
Widget::IconLabel(x) => &mut x.tooltip_description,
409372
Widget::ImageButton(x) => &mut x.tooltip_description,
410373
Widget::ImageLabel(x) => &mut x.tooltip_description,
411374
Widget::NumberInput(x) => &mut x.tooltip_description,
412-
Widget::ParameterExposeButton(x) => &mut x.tooltip_description,
413375
Widget::PopoverButton(x) => &mut x.tooltip_description,
414376
Widget::TextAreaInput(x) => &mut x.tooltip_description,
415377
Widget::TextButton(x) => &mut x.tooltip_description,
416378
Widget::TextInput(x) => &mut x.tooltip_description,
417379
Widget::TextLabel(x) => &mut x.tooltip_description,
418380
Widget::BreadcrumbTrailButtons(x) => &mut x.tooltip_description,
419-
Widget::ReferencePointInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::ShortcutLabel(_) | Widget::WorkingColorsInput(_) | Widget::NodeCatalog(_) => continue,
381+
Widget::ReferencePointInput(_)
382+
| Widget::RadioInput(_)
383+
| Widget::Separator(_)
384+
| Widget::ShortcutLabel(_)
385+
| Widget::WorkingColorsInput(_)
386+
| Widget::NodeCatalog(_)
387+
| Widget::ParameterExposeButton(_) => continue,
420388
};
421389
if val.is_empty() {
422390
val.clone_from(&description);
@@ -727,7 +695,6 @@ pub enum Widget {
727695
ColorInput(ColorInput),
728696
CurveInput(CurveInput),
729697
DropdownInput(DropdownInput),
730-
FontInput(FontInput),
731698
IconButton(IconButton),
732699
IconLabel(IconLabel),
733700
ImageButton(ImageButton),
@@ -782,7 +749,6 @@ impl DiffUpdate {
782749
Widget::CheckboxInput(widget) => widget.tooltip_shortcut.as_mut(),
783750
Widget::ColorInput(widget) => widget.tooltip_shortcut.as_mut(),
784751
Widget::DropdownInput(widget) => widget.tooltip_shortcut.as_mut(),
785-
Widget::FontInput(widget) => widget.tooltip_shortcut.as_mut(),
786752
Widget::IconButton(widget) => widget.tooltip_shortcut.as_mut(),
787753
Widget::NumberInput(widget) => widget.tooltip_shortcut.as_mut(),
788754
Widget::ParameterExposeButton(widget) => widget.tooltip_shortcut.as_mut(),
@@ -838,10 +804,38 @@ impl DiffUpdate {
838804
(recursive_wrapper.0)(entry_sections, &recursive_wrapper)
839805
};
840806

807+
// Hash the menu list entry sections for caching purposes
808+
let hash_menu_list_entry_sections = |entry_sections: &MenuListEntrySections| {
809+
struct RecursiveHasher<'a> {
810+
hasher: DefaultHasher,
811+
hash_fn: &'a dyn Fn(&mut RecursiveHasher, &MenuListEntrySections),
812+
}
813+
let mut recursive_hasher = RecursiveHasher {
814+
hasher: DefaultHasher::new(),
815+
hash_fn: &|recursive_hasher, entry_sections| {
816+
for (index, entries) in entry_sections.iter().enumerate() {
817+
index.hash(&mut recursive_hasher.hasher);
818+
for entry in entries {
819+
entry.hash(&mut recursive_hasher.hasher);
820+
(recursive_hasher.hash_fn)(recursive_hasher, &entry.children);
821+
}
822+
}
823+
},
824+
};
825+
(recursive_hasher.hash_fn)(&mut recursive_hasher, entry_sections);
826+
recursive_hasher.hasher.finish()
827+
};
828+
841829
// Apply shortcut conversions to all widgets that have menu lists
842830
let convert_menu_lists = |widget_instance: &mut WidgetInstance| match &mut widget_instance.widget {
843-
Widget::DropdownInput(dropdown_input) => apply_action_shortcut_to_menu_lists(&mut dropdown_input.entries),
844-
Widget::TextButton(text_button) => apply_action_shortcut_to_menu_lists(&mut text_button.menu_list_children),
831+
Widget::DropdownInput(dropdown_input) => {
832+
apply_action_shortcut_to_menu_lists(&mut dropdown_input.entries);
833+
dropdown_input.entries_hash = hash_menu_list_entry_sections(&dropdown_input.entries);
834+
}
835+
Widget::TextButton(text_button) => {
836+
apply_action_shortcut_to_menu_lists(&mut text_button.menu_list_children);
837+
text_button.menu_list_children_hash = hash_menu_list_entry_sections(&text_button.menu_list_children);
838+
}
845839
_ => {}
846840
};
847841

editor/src/messages/layout/utility_types/widgets/button_widgets.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ pub struct TextButton {
144144
#[serde(rename = "menuListChildren")]
145145
pub menu_list_children: MenuListEntrySections,
146146

147+
#[serde(rename = "menuListChildrenHash")]
148+
#[widget_builder(skip)]
149+
pub menu_list_children_hash: u64,
150+
147151
// Callbacks
148152
#[serde(skip)]
149153
#[derivative(Debug = "ignore", PartialEq = "ignore")]

editor/src/messages/layout/utility_types/widgets/input_widgets.rs

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ pub struct DropdownInput {
8080
#[widget_builder(constructor)]
8181
pub entries: MenuListEntrySections,
8282

83+
#[serde(rename = "entriesHash")]
84+
#[widget_builder(skip)]
85+
pub entries_hash: u64,
86+
8387
// This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI)
8488
#[serde(rename = "selectedIndex")]
8589
pub selected_index: Option<u32>,
@@ -94,6 +98,9 @@ pub struct DropdownInput {
9498

9599
pub narrow: bool,
96100

101+
#[serde(rename = "virtualScrolling")]
102+
pub virtual_scrolling: bool,
103+
97104
#[serde(rename = "tooltipLabel")]
98105
pub tooltip_label: String,
99106

@@ -142,6 +149,10 @@ pub struct MenuListEntry {
142149

143150
pub children: MenuListEntrySections,
144151

152+
#[serde(rename = "childrenHash")]
153+
#[widget_builder(skip)]
154+
pub children_hash: u64,
155+
145156
// Callbacks
146157
#[serde(skip)]
147158
#[derivative(Debug = "ignore", PartialEq = "ignore")]
@@ -152,39 +163,13 @@ pub struct MenuListEntry {
152163
pub on_commit: WidgetCallback<()>,
153164
}
154165

155-
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
156-
#[derivative(Debug, PartialEq, Default)]
157-
pub struct FontInput {
158-
#[serde(rename = "fontFamily")]
159-
#[widget_builder(constructor)]
160-
pub font_family: String,
161-
162-
#[serde(rename = "fontStyle")]
163-
#[widget_builder(constructor)]
164-
pub font_style: String,
165-
166-
#[serde(rename = "isStyle")]
167-
pub is_style_picker: bool,
168-
169-
pub disabled: bool,
170-
171-
#[serde(rename = "tooltipLabel")]
172-
pub tooltip_label: String,
173-
174-
#[serde(rename = "tooltipDescription")]
175-
pub tooltip_description: String,
176-
177-
#[serde(rename = "tooltipShortcut")]
178-
pub tooltip_shortcut: Option<ActionShortcut>,
179-
180-
// Callbacks
181-
#[serde(skip)]
182-
#[derivative(Debug = "ignore", PartialEq = "ignore")]
183-
pub on_update: WidgetCallback<FontInput>,
184-
185-
#[serde(skip)]
186-
#[derivative(Debug = "ignore", PartialEq = "ignore")]
187-
pub on_commit: WidgetCallback<()>,
166+
impl std::hash::Hash for MenuListEntry {
167+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
168+
self.value.hash(state);
169+
self.label.hash(state);
170+
self.icon.hash(state);
171+
self.disabled.hash(state);
172+
}
188173
}
189174

190175
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]

0 commit comments

Comments
 (0)