mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-21 07:08:11 +08:00
Improve font import; replace Inconsolata with Source Code Pro; show third-party licenses in editor dialog (#3079)
* Improve font import; replace Inconsolata with Source Code Pro; show third-party licenses in editor dialog * Code review
This commit is contained in:
@@ -33,6 +33,9 @@ pub enum DialogMessage {
|
||||
RequestLicensesDialogWithLocalizedCommitDate {
|
||||
localized_commit_year: String,
|
||||
},
|
||||
RequestLicensesThirdPartyDialogWithLicenseText {
|
||||
license_text: String,
|
||||
},
|
||||
RequestNewDocumentDialog,
|
||||
RequestPreferencesDialog,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::new_document_dialog::NewDocumentDialogMessageContext;
|
||||
use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArtworkDialog, LicensesDialog};
|
||||
use crate::messages::dialog::simple_dialogs::LicensesThirdPartyDialog;
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportBounds;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
@@ -103,6 +104,10 @@ impl MessageHandler<DialogMessage, DialogMessageContext<'_>> for DialogMessageHa
|
||||
|
||||
dialog.send_dialog_to_frontend(responses);
|
||||
}
|
||||
DialogMessage::RequestLicensesThirdPartyDialogWithLicenseText { license_text } => {
|
||||
let dialog = LicensesThirdPartyDialog { license_text };
|
||||
dialog.send_dialog_to_frontend(responses);
|
||||
}
|
||||
DialogMessage::RequestNewDocumentDialog => {
|
||||
self.new_document_dialog = NewDocumentDialogMessageHandler {
|
||||
name: portfolio.generate_new_document_name(),
|
||||
|
||||
@@ -92,13 +92,13 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
||||
.collect();
|
||||
|
||||
let export_type = vec![
|
||||
TextLabel::new("File Type").table_align(true).min_width(100).widget_holder(),
|
||||
TextLabel::new("File Type").table_align(true).min_width("100px").widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
RadioInput::new(entries).selected_index(Some(self.file_type as u32)).widget_holder(),
|
||||
];
|
||||
|
||||
let resolution = vec![
|
||||
TextLabel::new("Scale Factor").table_align(true).min_width(100).widget_holder(),
|
||||
TextLabel::new("Scale Factor").table_align(true).min_width("100px").widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
NumberInput::new(Some(self.scale_factor))
|
||||
.unit("")
|
||||
@@ -144,14 +144,14 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
||||
}
|
||||
|
||||
let export_area = vec![
|
||||
TextLabel::new("Bounds").table_align(true).min_width(100).widget_holder(),
|
||||
TextLabel::new("Bounds").table_align(true).min_width("100px").widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
DropdownInput::new(entries).selected_index(Some(index as u32)).widget_holder(),
|
||||
];
|
||||
|
||||
let checkbox_id = CheckboxId::new();
|
||||
let transparent_background = vec![
|
||||
TextLabel::new("Transparency").table_align(true).min_width(100).for_checkbox(checkbox_id).widget_holder(),
|
||||
TextLabel::new("Transparency").table_align(true).min_width("100px").for_checkbox(checkbox_id).widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
CheckboxInput::new(self.transparent_background)
|
||||
.disabled(self.file_type == FileType::Jpg)
|
||||
|
||||
+3
-3
@@ -79,7 +79,7 @@ impl DialogLayoutHolder for NewDocumentDialogMessageHandler {
|
||||
impl LayoutHolder for NewDocumentDialogMessageHandler {
|
||||
fn layout(&self) -> Layout {
|
||||
let name = vec![
|
||||
TextLabel::new("Name").table_align(true).min_width(90).widget_holder(),
|
||||
TextLabel::new("Name").table_align(true).min_width("90px").widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
TextInput::new(&self.name)
|
||||
.on_update(|text_input: &TextInput| NewDocumentDialogMessage::Name { name: text_input.value.clone() }.into())
|
||||
@@ -89,7 +89,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
|
||||
|
||||
let checkbox_id = CheckboxId::new();
|
||||
let infinite = vec![
|
||||
TextLabel::new("Infinite Canvas").table_align(true).min_width(90).for_checkbox(checkbox_id).widget_holder(),
|
||||
TextLabel::new("Infinite Canvas").table_align(true).min_width("90px").for_checkbox(checkbox_id).widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
CheckboxInput::new(self.infinite)
|
||||
.on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite { infinite: checkbox_input.checked }.into())
|
||||
@@ -98,7 +98,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
|
||||
];
|
||||
|
||||
let scale = vec![
|
||||
TextLabel::new("Dimensions").table_align(true).min_width(90).widget_holder(),
|
||||
TextLabel::new("Dimensions").table_align(true).min_width("90px").widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
NumberInput::new(Some(self.dimensions.x as f64))
|
||||
.label("W")
|
||||
|
||||
@@ -16,22 +16,31 @@ impl DialogLayoutHolder for LicensesDialog {
|
||||
}
|
||||
|
||||
fn layout_column_2(&self) -> Layout {
|
||||
let icons_license_link = "https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/frontend/assets/LICENSE.md";
|
||||
let links = [
|
||||
("GraphiteLogo", "Graphite Logo", "https://graphite.rs/logo/"),
|
||||
("IconsGrid", "Graphite Icons", icons_license_link),
|
||||
("License", "Graphite License", "https://graphite.rs/license/"),
|
||||
("License", "Other Licenses", "/third-party-licenses.txt"),
|
||||
#[allow(clippy::type_complexity)]
|
||||
let button_definitions: &[(&str, &str, fn() -> Message)] = &[
|
||||
("GraphiteLogo", "Graphite Logo", || {
|
||||
FrontendMessage::TriggerVisitLink {
|
||||
url: "https://graphite.rs/logo/".into(),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
("IconsGrid", "Graphite Icons", || {
|
||||
FrontendMessage::TriggerVisitLink {
|
||||
url: "https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/frontend/assets/LICENSE.md".into(),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
("License", "Graphite License", || {
|
||||
FrontendMessage::TriggerVisitLink {
|
||||
url: "https://graphite.rs/license/".into(),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
("License", "Other Licenses", || FrontendMessage::TriggerDisplayThirdPartyLicensesDialog.into()),
|
||||
];
|
||||
let widgets = links
|
||||
.into_iter()
|
||||
.map(|(icon, label, url)| {
|
||||
TextButton::new(label)
|
||||
.icon(Some(icon.into()))
|
||||
.flush(true)
|
||||
.on_update(|_| FrontendMessage::TriggerVisitLink { url: url.into() }.into())
|
||||
.widget_holder()
|
||||
})
|
||||
let widgets = button_definitions
|
||||
.iter()
|
||||
.map(|&(icon, label, message_factory)| TextButton::new(label).icon(Some((icon).into())).flush(true).on_update(move |_| message_factory()).widget_holder())
|
||||
.collect();
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Column { widgets }]))
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
pub struct LicensesThirdPartyDialog {
|
||||
pub license_text: String,
|
||||
}
|
||||
|
||||
impl DialogLayoutHolder for LicensesThirdPartyDialog {
|
||||
const ICON: &'static str = "License12px";
|
||||
const TITLE: &'static str = "Third-Party Software License Notices";
|
||||
|
||||
fn layout_buttons(&self) -> Layout {
|
||||
let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DisplayDialogDismiss.into()).widget_holder()];
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutHolder for LicensesThirdPartyDialog {
|
||||
fn layout(&self) -> Layout {
|
||||
// Remove the header and begin with the line containing the first license section (we otherwise keep the title for standalone viewing of the licenses text file)
|
||||
let license_text = if let Some(first_underscore_line) = self.license_text.lines().position(|line| line.contains('_')) {
|
||||
// Find the byte position where the line with underscore starts
|
||||
let char_position = self.license_text.split('\n').take(first_underscore_line).map(|line| line.len() + '\n'.len_utf8()).sum();
|
||||
self.license_text[char_position..].to_string()
|
||||
} else {
|
||||
// This shouldn't be encountered, but if no underscore line is found, we use the full text as a safety fallback
|
||||
self.license_text.clone()
|
||||
};
|
||||
|
||||
// Two characters (one before, one after) the sequence of underscore characters, plus one additional column to provide a space between the text and the scrollbar
|
||||
let non_wrapping_column_width = license_text.split('\n').map(|line| line.chars().filter(|&c| c == '_').count()).max().unwrap_or(0) + 2 + 1;
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
TextLabel::new(license_text)
|
||||
.monospace(true)
|
||||
.multiline(true)
|
||||
.min_width(format!("{non_wrapping_column_width}ch"))
|
||||
.widget_holder(),
|
||||
],
|
||||
}]))
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ mod coming_soon_dialog;
|
||||
mod demo_artwork_dialog;
|
||||
mod error_dialog;
|
||||
mod licenses_dialog;
|
||||
mod licenses_third_party_dialog;
|
||||
|
||||
pub use about_graphite_dialog::AboutGraphiteDialog;
|
||||
pub use close_all_documents_dialog::CloseAllDocumentsDialog;
|
||||
@@ -14,3 +15,4 @@ pub use demo_artwork_dialog::ARTWORK;
|
||||
pub use demo_artwork_dialog::DemoArtworkDialog;
|
||||
pub use error_dialog::ErrorDialog;
|
||||
pub use licenses_dialog::LicensesDialog;
|
||||
pub use licenses_third_party_dialog::LicensesThirdPartyDialog;
|
||||
|
||||
@@ -64,6 +64,7 @@ pub enum FrontendMessage {
|
||||
#[serde(rename = "commitDate")]
|
||||
commit_date: String,
|
||||
},
|
||||
TriggerDisplayThirdPartyLicensesDialog,
|
||||
TriggerSaveDocument {
|
||||
document_id: DocumentId,
|
||||
name: String,
|
||||
|
||||
@@ -44,16 +44,18 @@ pub struct TextLabel {
|
||||
|
||||
pub italic: bool,
|
||||
|
||||
pub monospace: bool,
|
||||
|
||||
pub multiline: bool,
|
||||
|
||||
#[serde(rename = "centerAlign")]
|
||||
pub center_align: bool,
|
||||
|
||||
#[serde(rename = "tableAlign")]
|
||||
pub table_align: bool,
|
||||
|
||||
pub multiline: bool,
|
||||
|
||||
#[serde(rename = "minWidth")]
|
||||
pub min_width: u32,
|
||||
pub min_width: String,
|
||||
|
||||
pub tooltip: String,
|
||||
|
||||
|
||||
@@ -1021,6 +1021,8 @@ impl OverlayContextInternal {
|
||||
};
|
||||
|
||||
// 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.
|
||||
const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
|
||||
let font_blob = Some(load_font(FONT_DATA));
|
||||
|
||||
@@ -1046,6 +1048,8 @@ impl OverlayContextInternal {
|
||||
};
|
||||
|
||||
// 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.
|
||||
const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
|
||||
let font_blob = Some(load_font(FONT_DATA));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user