diff --git a/data/strings/en.ini b/data/strings/en.ini index 5a0ab6201..e35cf5fab 100644 --- a/data/strings/en.ini +++ b/data/strings/en.ini @@ -1513,6 +1513,12 @@ alpha_range = Alpha Range: opacity_range = Opacity Range: 8bit_value = 0-255 percentage = 0%-100% +customize_theme = Customize Theme +font = Font: +mini_font = Mini Font: +preview = Preview: +font_preview = Font Preview +mini_font_preview = Mini Font Preview available_themes = Available Themes extension_themes = Extension Themes select_theme = &Select diff --git a/data/widgets/options.xml b/data/widgets/options.xml index 35e89b840..752bf0270 100644 --- a/data/widgets/options.xml +++ b/data/widgets/options.xml @@ -1,5 +1,5 @@ - + @@ -537,6 +537,20 @@ + + + diff --git a/src/app/commands/cmd_options.cpp b/src/app/commands/cmd_options.cpp index 442c6ca19..9477159ea 100644 --- a/src/app/commands/cmd_options.cpp +++ b/src/app/commands/cmd_options.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2024 Igara Studio S.A. +// Copyright (C) 2018-2025 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -35,6 +35,7 @@ #include "app/ui/sampling_selector.h" #include "app/ui/separator_in_view.h" #include "app/ui/skin/skin_theme.h" +#include "app/util/render_text.h" #include "base/convert_to.h" #include "base/fs.h" #include "base/string.h" @@ -267,8 +268,10 @@ public: { sectionListbox()->Change.connect([this] { onChangeSection(); }); - // Theme variants + // Theme variants/fonts. fillThemeVariants(); + fillThemeFonts(); + updateFontPreviews(); // Recent files clearRecentFiles()->Click.connect([this] { onClearRecentFiles(); }); @@ -440,6 +443,22 @@ public: // Undo preferences limitUndo()->Click.connect([this] { onLimitUndoCheck(); }); + // Theme Custom Font + customThemeFont()->Click.connect([this] { + const bool state = customThemeFont()->isSelected(); + themeFont()->setEnabled(state); + if (!state) + themeFont()->setInfo(FontInfo(), FontEntry::From::Init); + }); + customMiniFont()->Click.connect([this] { + const bool state = customMiniFont()->isSelected(); + themeMiniFont()->setEnabled(state); + if (!state) + themeMiniFont()->setInfo(FontInfo(), FontEntry::From::Init); + }); + themeFont()->FontChange.connect([this] { updateFontPreviews(); }); + themeMiniFont()->FontChange.connect([this] { updateFontPreviews(); }); + // Theme buttons themeList()->Change.connect([this] { onThemeChange(); }); themeList()->DoubleClickItem.connect([this] { onSelectTheme(); }); @@ -893,6 +912,23 @@ public: ui::set_use_native_cursors(m_pref.cursor.useNativeCursor()); ui::set_mouse_cursor_scale(m_pref.cursor.cursorScale()); + // Change theme font + bool reset_theme = false; + { + const FontInfo fontInfo = themeFont()->info(); + const FontInfo miniFontInfo = themeMiniFont()->info(); + + auto fontStr = base::convert_to(fontInfo); + auto miniFontStr = base::convert_to(miniFontInfo); + + if (m_pref.theme.font() != fontStr || m_pref.theme.miniFont() != miniFontStr) { + m_pref.theme.font(fontStr); + m_pref.theme.miniFont(miniFontStr); + + reset_theme = true; + } + } + bool reset_screen = false; const int newScreenScale = base::convert_to(screenScale()->getValue()); if (newScreenScale != m_pref.general.screenScale()) { @@ -903,10 +939,13 @@ public: const int newUIScale = base::convert_to(uiScale()->getValue()); if (newUIScale != m_pref.general.uiScale()) { m_pref.general.uiScale(newUIScale); - ui::set_theme(ui::get_theme(), newUIScale); + reset_theme = true; reset_screen = true; } + if (reset_theme) + ui::set_theme(ui::get_theme(), newUIScale); + #ifdef ENABLE_DEVMODE const bool newGpuAccel = gpuAcceleration()->isSelected(); if (newGpuAccel != m_pref.general.gpuAcceleration()) { @@ -989,6 +1028,14 @@ public: } private: + void onInitTheme(InitThemeEvent& ev) override + { + app::gen::Options::onInitTheme(ev); + + fontPreview()->setFont(m_font); + miniFontPreview()->setFont(m_miniFont); + } + void fillThemeVariants() { ButtonSet* list = nullptr; @@ -1027,6 +1074,34 @@ private: themeVariants()->initTheme(); } + void fillThemeFonts() + { + auto& pref = Preferences::instance(); + const FontInfo fontInfo = base::convert_to(pref.theme.font()); + const FontInfo miniInfo = base::convert_to(pref.theme.miniFont()); + + customThemeFont()->setSelected(fontInfo.isValid()); + customMiniFont()->setSelected(miniInfo.isValid()); + + themeFont()->setEnabled(fontInfo.isValid()); + themeMiniFont()->setEnabled(miniInfo.isValid()); + + themeFont()->setInfo(fontInfo, FontEntry::From::Init); + themeMiniFont()->setInfo(miniInfo, FontEntry::From::Init); + } + + void updateFontPreviews() + { + m_font = get_font_from_info(themeFont()->info()); + m_miniFont = get_font_from_info(themeMiniFont()->info()); + if (!m_miniFont) + m_miniFont = skin::SkinTheme::get(this)->getMiniFont(); + + fontPreview()->setFont(m_font); + miniFontPreview()->setFont(m_miniFont); + layout(); + } + void fillExtensionsCombobox(ui::ComboBox* combobox, const std::string& defExt) { base::paths exts = get_writable_extensions(); @@ -2034,6 +2109,8 @@ private: BestFitCriteriaSelector m_bestFitCriteriaSelector; ButtonSet* m_themeVars = nullptr; SamplingSelector* m_samplingSelector = nullptr; + text::FontRef m_font; + text::FontRef m_miniFont; }; class OptionsCommand : public Command { diff --git a/src/app/font_info.cpp b/src/app/font_info.cpp index 140aeb6c2..6f5470717 100644 --- a/src/app/font_info.cpp +++ b/src/app/font_info.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (c) 2024 Igara Studio S.A. +// Copyright (c) 2024-2025 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -159,7 +159,7 @@ app::FontInfo convert_to(const std::string& from) type = app::FontInfo::Type::System; name = parts[0].substr(7); } - else { + else if (!parts[0].empty()) { type = app::FontInfo::Type::Name; name = parts[0]; } diff --git a/src/app/ui/skin/skin_theme.cpp b/src/app/ui/skin/skin_theme.cpp index 558f86aec..9003a39f5 100644 --- a/src/app/ui/skin/skin_theme.cpp +++ b/src/app/ui/skin/skin_theme.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2024 Igara Studio S.A. +// Copyright (C) 2019-2025 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -14,6 +14,7 @@ #include "app/app.h" #include "app/console.h" #include "app/extensions.h" +#include "app/font_info.h" #include "app/font_path.h" #include "app/modules/gui.h" #include "app/pref/preferences.h" @@ -23,6 +24,7 @@ #include "app/ui/skin/font_data.h" #include "app/ui/skin/skin_property.h" #include "app/ui/skin/skin_slider_property.h" +#include "app/util/render_text.h" #include "app/xml_document.h" #include "app/xml_exception.h" #include "base/fs.h" @@ -482,6 +484,19 @@ void SkinTheme::loadXml(BackwardCompatibility* backward) if (!m_miniFont) m_miniFont = m_defaultFont; + // Overwrite theme fonts by user defined fonts. + Preferences& pref = Preferences::instance(); + if (!pref.theme.font().empty()) { + auto fi = base::convert_to(pref.theme.font()); + if (auto f = get_font_from_info(fi, this)) + m_defaultFont = f; + } + if (!pref.theme.miniFont().empty()) { + auto fi = base::convert_to(pref.theme.miniFont()); + if (auto f = get_font_from_info(fi, this)) + m_miniFont = f; + } + // Load dimension { XMLElement* xmlDim = handle.FirstChildElement("theme") @@ -781,9 +796,21 @@ void SkinTheme::loadXml(BackwardCompatibility* backward) { const char* fontId = xmlStyle->Attribute("font"); if (fontId) { - auto themeFont = m_themeFonts[fontId]; - style->setFont(themeFont.font()); - style->setMnemonics(themeFont.mnemonics()); + // Use m_defaultFont/m_miniFont just in case the user + // customized these fonts. + if (std::strcmp(fontId, "default") == 0) { + style->setFont(m_defaultFont); + style->setMnemonics(true); + } + else if (std::strcmp(fontId, "mini") == 0) { + style->setFont(m_miniFont); + style->setMnemonics(false); + } + else { + auto themeFont = m_themeFonts[fontId]; + style->setFont(themeFont.font()); + style->setMnemonics(themeFont.mnemonics()); + } } // Override mnemonics value if it is defined for this style. diff --git a/src/app/util/render_text.cpp b/src/app/util/render_text.cpp index 8249c4580..7b79abbc1 100644 --- a/src/app/util/render_text.cpp +++ b/src/app/util/render_text.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2022-2024 Igara Studio S.A. +// Copyright (C) 2022-2025 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -63,12 +63,14 @@ private: } // anonymous namespace -text::FontRef get_font_from_info(const FontInfo& fontInfo) +text::FontRef get_font_from_info(const FontInfo& fontInfo, skin::SkinTheme* theme) { - auto* theme = skin::SkinTheme::instance(); - ASSERT(theme); - if (!theme) - return nullptr; + if (!theme) { + theme = skin::SkinTheme::instance(); + ASSERT(theme); + if (!theme) + return nullptr; + } const text::FontMgrRef fontMgr = theme->fontMgr(); if (!fontMgr) diff --git a/src/app/util/render_text.h b/src/app/util/render_text.h index 9fa35e33d..8ab8da9b5 100644 --- a/src/app/util/render_text.h +++ b/src/app/util/render_text.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2024 Igara Studio S.A. +// Copyright (C) 2024-2025 Igara Studio S.A. // Copyright (C) 2001-2015 David Capello // // This program is distributed under the terms of @@ -20,8 +20,11 @@ namespace app { class Color; class FontInfo; +namespace skin { +class SkinTheme; +} -text::FontRef get_font_from_info(const FontInfo& fontInfo); +text::FontRef get_font_from_info(const FontInfo& fontInfo, skin::SkinTheme* theme = nullptr); text::TextBlobRef create_text_blob(const FontInfo& fontInfo, const std::string& text);