2015-02-12 23:16:25 +08:00
|
|
|
// Aseprite
|
2025-02-13 04:31:27 +08:00
|
|
|
// Copyright (C) 2019-2025 Igara Studio S.A.
|
2018-01-30 01:09:22 +08:00
|
|
|
// Copyright (C) 2001-2018 David Capello
|
2015-02-12 23:16:25 +08:00
|
|
|
//
|
2016-08-27 04:02:58 +08:00
|
|
|
// This program is distributed under the terms of
|
|
|
|
// the End-User License Agreement for Aseprite.
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
#ifdef HAVE_CONFIG_H
|
2010-03-08 00:21:24 +08:00
|
|
|
#include "config.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#endif
|
2010-03-08 00:21:24 +08:00
|
|
|
|
2017-06-11 02:02:39 +08:00
|
|
|
#include "app/ui/skin/skin_theme.h"
|
|
|
|
|
|
|
|
#include "app/app.h"
|
2016-10-27 21:44:31 +08:00
|
|
|
#include "app/console.h"
|
2017-06-11 02:02:39 +08:00
|
|
|
#include "app/extensions.h"
|
2025-03-04 09:36:11 +08:00
|
|
|
#include "app/fonts/font_data.h"
|
|
|
|
#include "app/fonts/font_info.h"
|
|
|
|
#include "app/fonts/font_path.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/modules/gui.h"
|
2015-09-18 22:56:45 +08:00
|
|
|
#include "app/pref/preferences.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/resource_finder.h"
|
2014-10-29 22:58:03 +08:00
|
|
|
#include "app/ui/app_menuitem.h"
|
|
|
|
#include "app/ui/keyboard_shortcuts.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/ui/skin/skin_property.h"
|
|
|
|
#include "app/ui/skin/skin_slider_property.h"
|
2025-02-13 04:31:27 +08:00
|
|
|
#include "app/util/render_text.h"
|
2013-10-15 06:58:11 +08:00
|
|
|
#include "app/xml_document.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/xml_exception.h"
|
2013-10-15 06:58:11 +08:00
|
|
|
#include "base/fs.h"
|
2017-08-23 01:54:08 +08:00
|
|
|
#include "base/log.h"
|
2014-09-21 22:59:39 +08:00
|
|
|
#include "base/string.h"
|
2022-04-22 07:24:49 +08:00
|
|
|
#include "base/utf8_decode.h"
|
2025-04-18 06:40:28 +08:00
|
|
|
#include "fmt/format.h"
|
2011-02-05 23:03:22 +08:00
|
|
|
#include "gfx/border.h"
|
|
|
|
#include "gfx/point.h"
|
2011-01-24 11:03:38 +08:00
|
|
|
#include "gfx/rect.h"
|
2011-02-05 23:03:22 +08:00
|
|
|
#include "gfx/size.h"
|
2018-08-09 23:58:43 +08:00
|
|
|
#include "os/surface.h"
|
|
|
|
#include "os/system.h"
|
2024-02-21 02:40:17 +08:00
|
|
|
#include "text/draw_text.h"
|
|
|
|
#include "text/font.h"
|
2025-03-25 10:04:50 +08:00
|
|
|
#include "text/font_metrics.h"
|
2025-04-18 06:40:28 +08:00
|
|
|
#include "text/font_style_set.h"
|
2025-06-12 04:05:40 +08:00
|
|
|
#include "text/text_blob.h"
|
2012-06-18 09:49:58 +08:00
|
|
|
#include "ui/intern.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "ui/ui.h"
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2024-04-23 05:28:03 +08:00
|
|
|
#include "tinyxml2.h"
|
- All tools stuff refactored in various files/components.
- Added classes: IToolLoop, Tool, ToolGroup, ToolInk, ToolController, ToolPointShape, ToolIntertwine, ToolBox, etc.
- Added ToolLoopManager.
- Removed old src/modules/tools.cpp.
- Added ISettings and UISettingsImpl, adding the tools settings (onion skinning, grid, tiled mode, etc.).
- Added App::PenSizeBeforeChange, PenSizeAfterChange, CurrentToolChange signals.
- Renamed Context::get_bg/fg_color to getBg/FgColor.
- Refactored Brush class to Pen and added PenType.
- Renamed tiled_t to TiledMode.
- get_config_rect now uses the new Rect class imported from Vaca instead of old jrect.
- Added default_skin.xml to load tool icons.
- Added pen preview in Editor::cursor stuff.
- Added Editor::decorators.
Note: This big patch is from some time ago. I did my best to pre-commit other small changes before this big one.
2010-03-08 03:47:45 +08:00
|
|
|
|
2020-04-08 23:03:32 +08:00
|
|
|
#include <algorithm>
|
2025-03-25 10:04:50 +08:00
|
|
|
#include <cstdlib>
|
2017-03-16 02:13:23 +08:00
|
|
|
#include <cstring>
|
2023-03-01 01:28:09 +08:00
|
|
|
#include <memory>
|
2017-03-16 02:13:23 +08:00
|
|
|
|
2013-01-07 01:45:43 +08:00
|
|
|
#define BGCOLOR (getWidgetBgColor(widget))
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
namespace app { namespace skin {
|
|
|
|
|
|
|
|
using namespace gfx;
|
2024-04-23 05:28:03 +08:00
|
|
|
using namespace tinyxml2;
|
2013-08-06 08:20:19 +08:00
|
|
|
using namespace ui;
|
|
|
|
|
2017-06-11 02:02:39 +08:00
|
|
|
// TODO For backward compatibility, in future versions we should remove this (extensions are
|
|
|
|
// preferred)
|
2017-02-10 00:18:44 +08:00
|
|
|
const char* SkinTheme::kThemesFolderName = "themes";
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2023-03-01 01:28:09 +08:00
|
|
|
// Offers backward compatibility with old themes, copying missing
|
|
|
|
// styles (raw XML <style> elements) from the default theme to the
|
|
|
|
// current theme. This must be done so all <style> elements in the new
|
|
|
|
// theme use colors and parts from the new theme instead of the
|
|
|
|
// default one (as when they were loaded).
|
|
|
|
class app::skin::SkinTheme::BackwardCompatibility {
|
|
|
|
enum class State {
|
|
|
|
// When we are loading the default theme
|
|
|
|
LoadingStyles,
|
|
|
|
// When we are loading the selected theme (so we must copy missing
|
|
|
|
// styles from the previously loaded default theme)
|
|
|
|
CopyingStyles,
|
|
|
|
};
|
|
|
|
|
|
|
|
State m_state = State::LoadingStyles;
|
|
|
|
|
|
|
|
// Loaded XML <style> element from the original theme (cloned
|
|
|
|
// elements). Must be in order to insert them in the same order in
|
|
|
|
// the selected theme.
|
2024-04-23 05:28:03 +08:00
|
|
|
XMLDocumentRef m_stylesDoc;
|
|
|
|
std::vector<XMLElement*> m_styles;
|
2023-03-01 01:28:09 +08:00
|
|
|
|
|
|
|
public:
|
|
|
|
void copyingStyles() { m_state = State::CopyingStyles; }
|
|
|
|
|
|
|
|
// Called for each <style> element found in theme.xml.
|
2024-04-23 05:28:03 +08:00
|
|
|
void onStyle(XMLElement* xmlStyle)
|
|
|
|
{
|
2023-03-01 01:28:09 +08:00
|
|
|
// Loading <style> from the default theme
|
2024-04-23 05:28:03 +08:00
|
|
|
if (m_state == State::LoadingStyles) {
|
|
|
|
if (!m_stylesDoc)
|
|
|
|
m_stylesDoc = std::make_unique<XMLDocument>();
|
|
|
|
m_styles.emplace_back(xmlStyle->DeepClone(m_stylesDoc.get())->ToElement());
|
|
|
|
}
|
2023-03-01 01:28:09 +08:00
|
|
|
}
|
|
|
|
|
2024-04-23 05:28:03 +08:00
|
|
|
void removeExistentStyles(XMLElement* xmlStyle)
|
|
|
|
{
|
2023-03-01 01:28:09 +08:00
|
|
|
if (m_state != State::CopyingStyles)
|
|
|
|
return;
|
|
|
|
|
|
|
|
while (xmlStyle) {
|
|
|
|
const char* s = xmlStyle->Attribute("id");
|
|
|
|
if (!s)
|
|
|
|
break;
|
|
|
|
std::string styleId = s;
|
|
|
|
|
|
|
|
// Remove any existent style in the selected theme.
|
|
|
|
auto it = std::find_if(m_styles.begin(), m_styles.end(), [styleId](auto& style) {
|
|
|
|
return (style->Attribute("id") == styleId);
|
|
|
|
});
|
|
|
|
if (it != m_styles.end())
|
|
|
|
m_styles.erase(it);
|
|
|
|
|
|
|
|
xmlStyle = xmlStyle->NextSiblingElement();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copies all missing <style> elements to the new theme. xmlStyles
|
|
|
|
// is the <styles> element from the theme.xml of the selected theme
|
|
|
|
// (non the default one).
|
2024-04-23 05:28:03 +08:00
|
|
|
void copyMissingStyles(XMLNode* xmlStyles)
|
|
|
|
{
|
2023-03-01 01:28:09 +08:00
|
|
|
if (m_state != State::CopyingStyles)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (auto& style : m_styles) {
|
|
|
|
LOG(VERBOSE, "THEME: Copying <style id='%s'> from default theme\n", style->Attribute("id"));
|
|
|
|
|
2024-04-23 05:28:03 +08:00
|
|
|
xmlStyles->InsertEndChild(style->DeepClone(xmlStyles->GetDocument()));
|
2020-05-02 10:31:10 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-03-15 06:00:24 +08:00
|
|
|
static const char* g_cursor_names[kCursorTypes] = {
|
2012-08-11 10:14:54 +08:00
|
|
|
"null", // kNoCursor
|
|
|
|
"normal", // kArrowCursor
|
|
|
|
"normal_add", // kArrowPlusCursor
|
2016-08-31 22:22:08 +08:00
|
|
|
"crosshair", // kCrosshairCursor
|
2012-08-11 10:14:54 +08:00
|
|
|
"forbidden", // kForbiddenCursor
|
|
|
|
"hand", // kHandCursor
|
|
|
|
"scroll", // kScrollCursor
|
|
|
|
"move", // kMoveCursor
|
2014-08-11 06:51:14 +08:00
|
|
|
"size_ns", // kSizeNSCursor
|
|
|
|
"size_we", // kSizeWECursor
|
|
|
|
"size_n", // kSizeNCursor
|
|
|
|
"size_ne", // kSizeNECursor
|
|
|
|
"size_e", // kSizeECursor
|
|
|
|
"size_se", // kSizeSECursor
|
|
|
|
"size_s", // kSizeSCursor
|
|
|
|
"size_sw", // kSizeSWCursor
|
|
|
|
"size_w", // kSizeWCursor
|
|
|
|
"size_nw", // kSizeNWCursor
|
2009-11-22 04:02:31 +08:00
|
|
|
};
|
|
|
|
|
2025-04-18 06:40:28 +08:00
|
|
|
static std::unique_ptr<FontData> try_to_load_system_font(const XMLElement* xmlFont)
|
|
|
|
{
|
|
|
|
const char* systemStr = xmlFont->Attribute("system");
|
|
|
|
if (!systemStr)
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
Fonts* fonts = Fonts::instance();
|
|
|
|
text::FontMgrRef fontMgr = fonts->fontMgr();
|
|
|
|
const text::FontStyleSetRef set = fontMgr->matchFamily(systemStr);
|
|
|
|
if (!set)
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
text::FontStyle style;
|
|
|
|
text::TypefaceRef typeface = set->matchStyle(style);
|
|
|
|
if (!typeface)
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
float size = 0.0f;
|
|
|
|
if (const char* sizeStr = xmlFont->Attribute("size"))
|
|
|
|
size = std::strtof(sizeStr, nullptr);
|
|
|
|
|
|
|
|
text::FontRef nativeFont;
|
|
|
|
if (size == 0.0f)
|
|
|
|
nativeFont = fontMgr->makeFont(typeface);
|
|
|
|
else
|
|
|
|
nativeFont = fontMgr->makeFont(typeface, size);
|
|
|
|
if (!nativeFont)
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
nativeFont->setAntialias(bool_attr(xmlFont, "antialias", false));
|
|
|
|
nativeFont->setHinting(bool_attr(xmlFont, "hinting", true) ? text::FontHinting::Normal :
|
|
|
|
text::FontHinting::None);
|
|
|
|
|
|
|
|
auto font = std::make_unique<FontData>(nativeFont);
|
|
|
|
font->setName(systemStr);
|
|
|
|
if (size != 0.0f)
|
|
|
|
font->setDefaultSize(size);
|
|
|
|
|
|
|
|
return font;
|
|
|
|
}
|
|
|
|
|
2025-03-05 00:27:39 +08:00
|
|
|
static FontData* load_font(const XMLElement* xmlFont, const std::string& xmlFilename)
|
2017-02-25 04:56:57 +08:00
|
|
|
{
|
2025-03-05 00:27:39 +08:00
|
|
|
Fonts* fonts = Fonts::instance();
|
|
|
|
ASSERT(fonts);
|
|
|
|
|
|
|
|
if (const char* fontId = xmlFont->Attribute("font")) {
|
|
|
|
if (FontData* fontData = fonts->fontDataByName(fontId))
|
|
|
|
return fontData;
|
2025-04-18 06:40:28 +08:00
|
|
|
else {
|
|
|
|
throw base::Exception(
|
|
|
|
fmt::format("{}:{}: Font named '{}' not found", xmlFilename, xmlFont->GetLineNum(), fontId));
|
|
|
|
}
|
2017-02-25 04:56:57 +08:00
|
|
|
}
|
|
|
|
|
2025-04-18 06:40:28 +08:00
|
|
|
const char* nameStr = xmlFont->Attribute("name"); // Optional name
|
2017-02-25 04:56:57 +08:00
|
|
|
const char* typeStr = xmlFont->Attribute("type");
|
2025-04-18 06:40:28 +08:00
|
|
|
const char* systemStr = xmlFont->Attribute("system");
|
|
|
|
|
|
|
|
LOG(VERBOSE,
|
|
|
|
"THEME: Loading font name='%s' type='%s' system='%s'\n",
|
|
|
|
nameStr ? nameStr : "",
|
|
|
|
typeStr ? systemStr : "",
|
|
|
|
systemStr ? systemStr : "");
|
2017-02-25 04:56:57 +08:00
|
|
|
|
2025-04-18 06:40:28 +08:00
|
|
|
std::string type(typeStr ? typeStr : "");
|
2017-10-09 22:55:20 +08:00
|
|
|
std::string xmlDir(base::get_file_path(xmlFilename));
|
2018-08-09 04:27:26 +08:00
|
|
|
std::unique_ptr<FontData> font(nullptr);
|
2017-02-25 04:56:57 +08:00
|
|
|
|
|
|
|
if (type == "spritesheet") {
|
|
|
|
const char* fileStr = xmlFont->Attribute("file");
|
|
|
|
if (fileStr) {
|
2025-03-05 00:27:39 +08:00
|
|
|
font = std::make_unique<FontData>(text::FontType::SpriteSheet);
|
2025-04-18 06:40:28 +08:00
|
|
|
font->setName(nameStr ? nameStr : fileStr);
|
2017-10-09 22:55:20 +08:00
|
|
|
font->setFilename(base::join_path(xmlDir, fileStr));
|
2025-03-25 10:04:50 +08:00
|
|
|
if (const char* descent = xmlFont->Attribute("descent"))
|
|
|
|
font->setDescent(std::strtof(descent, nullptr));
|
2017-02-25 04:56:57 +08:00
|
|
|
}
|
|
|
|
}
|
2025-04-18 06:40:28 +08:00
|
|
|
// Loads: <font type="truetype" file="..." />
|
|
|
|
// Or: <font type="truetype" file_win="..." file_mac="..." file_linux="..." />
|
2017-02-25 04:56:57 +08:00
|
|
|
else if (type == "truetype") {
|
|
|
|
const char* platformFileAttrName =
|
2025-04-18 06:40:28 +08:00
|
|
|
#if LAF_WINDOWS
|
2017-02-25 04:56:57 +08:00
|
|
|
"file_win"
|
2025-04-18 06:40:28 +08:00
|
|
|
#elif LAF_MACOS
|
2017-02-25 04:56:57 +08:00
|
|
|
"file_mac"
|
|
|
|
#else
|
|
|
|
"file_linux"
|
|
|
|
#endif
|
|
|
|
;
|
|
|
|
|
|
|
|
const char* platformFileStr = xmlFont->Attribute(platformFileAttrName);
|
|
|
|
const char* fileStr = xmlFont->Attribute("file");
|
|
|
|
bool antialias = true;
|
|
|
|
if (xmlFont->Attribute("antialias"))
|
2021-09-07 00:20:20 +08:00
|
|
|
antialias = bool_attr(xmlFont, "antialias", false);
|
2017-02-25 04:56:57 +08:00
|
|
|
|
2025-06-12 04:20:17 +08:00
|
|
|
text::FontHinting hinting = (bool_attr(xmlFont, "hinting", true) ? text::FontHinting::Normal :
|
|
|
|
text::FontHinting::None);
|
|
|
|
|
2017-02-25 04:56:57 +08:00
|
|
|
std::string fontFilename;
|
|
|
|
if (platformFileStr)
|
2017-10-09 22:55:20 +08:00
|
|
|
fontFilename = app::find_font(xmlDir, platformFileStr);
|
2017-02-25 04:56:57 +08:00
|
|
|
if (fileStr && fontFilename.empty())
|
2017-10-09 22:55:20 +08:00
|
|
|
fontFilename = app::find_font(xmlDir, fileStr);
|
2017-02-25 04:56:57 +08:00
|
|
|
|
2017-03-08 01:53:40 +08:00
|
|
|
// The filename can be empty if the font was not found, anyway we
|
|
|
|
// want to keep the font information (e.g. to use the fallback
|
|
|
|
// information of this font).
|
2025-03-05 00:27:39 +08:00
|
|
|
font = std::make_unique<FontData>(text::FontType::FreeType);
|
2025-04-18 06:40:28 +08:00
|
|
|
font->setName(nameStr ? nameStr : (platformFileStr ? platformFileStr : fileStr));
|
2017-03-08 01:53:40 +08:00
|
|
|
font->setFilename(fontFilename);
|
|
|
|
font->setAntialias(antialias);
|
2025-06-12 04:20:17 +08:00
|
|
|
font->setHinting(hinting);
|
2017-03-08 01:53:40 +08:00
|
|
|
|
|
|
|
if (!fontFilename.empty())
|
2020-06-18 04:05:43 +08:00
|
|
|
LOG(VERBOSE, "THEME: Font file '%s' found\n", fontFilename.c_str());
|
2017-02-25 04:56:57 +08:00
|
|
|
}
|
2025-04-18 06:40:28 +08:00
|
|
|
// Loads: <font system="..." />
|
|
|
|
// Or: <font>
|
|
|
|
// <windows system="..." />
|
|
|
|
// <macos system="..." />
|
|
|
|
// <linux system="..." />
|
|
|
|
// </font>
|
|
|
|
else if (!typeStr || type == "system") {
|
|
|
|
// Try to get the platform-specific font first
|
|
|
|
const char* platformName =
|
|
|
|
#if LAF_WINDOWS
|
|
|
|
"windows"
|
|
|
|
#elif LAF_MACOS
|
|
|
|
"macos"
|
|
|
|
#else
|
|
|
|
"linux"
|
|
|
|
#endif
|
|
|
|
;
|
|
|
|
|
|
|
|
// Load platform specific font from sub elements like
|
|
|
|
// <font>
|
|
|
|
// <windows system="..." />
|
|
|
|
// ...
|
|
|
|
// </font>
|
|
|
|
XMLElement* xmlPlatformFont = (XMLElement*)xmlFont->FirstChildElement(platformName);
|
|
|
|
while (xmlPlatformFont) {
|
|
|
|
if (auto platformFont = try_to_load_system_font(xmlPlatformFont)) {
|
|
|
|
font = std::move(platformFont);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
xmlPlatformFont = xmlPlatformFont->NextSiblingElement();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load the system font from
|
|
|
|
// <font system="..." />
|
|
|
|
if (!font && systemStr) {
|
|
|
|
font = try_to_load_system_font(xmlFont);
|
|
|
|
}
|
2025-06-11 23:49:52 +08:00
|
|
|
|
|
|
|
if (font && nameStr)
|
|
|
|
font->setName(nameStr);
|
2025-04-18 06:40:28 +08:00
|
|
|
}
|
2017-02-25 04:56:57 +08:00
|
|
|
else {
|
2025-04-18 06:40:28 +08:00
|
|
|
throw base::Exception(
|
|
|
|
fmt::format("{}:{}: Invalid font type (\"{}\")' attribute in <font /> element",
|
|
|
|
xmlFilename,
|
|
|
|
xmlFont->GetLineNum(),
|
|
|
|
type));
|
2017-02-25 04:56:57 +08:00
|
|
|
}
|
|
|
|
|
2025-04-18 06:40:28 +08:00
|
|
|
if (!font) {
|
|
|
|
if (!typeStr && !systemStr) {
|
|
|
|
throw base::Exception(
|
|
|
|
fmt::format("{}:{}: Missing 'font', 'type', or 'system' attributes in <font /> element",
|
|
|
|
xmlFilename,
|
|
|
|
xmlFont->GetLineNum()));
|
|
|
|
}
|
|
|
|
|
2025-03-05 00:27:39 +08:00
|
|
|
return nullptr;
|
2025-04-18 06:40:28 +08:00
|
|
|
}
|
2017-02-25 04:56:57 +08:00
|
|
|
|
2025-03-05 00:27:39 +08:00
|
|
|
FontData* result = font.get();
|
2025-04-18 06:40:28 +08:00
|
|
|
fonts->addFontData(std::move(font)); // "font" variable is invalid from now on
|
2017-02-25 04:56:57 +08:00
|
|
|
|
2025-03-05 00:27:39 +08:00
|
|
|
// Fallback font
|
|
|
|
const XMLElement* xmlFallback = (const XMLElement*)xmlFont->FirstChildElement("fallback");
|
|
|
|
if (xmlFallback) {
|
|
|
|
FontData* fallback = load_font(xmlFallback, xmlFilename);
|
|
|
|
if (fallback) {
|
|
|
|
int size = 10;
|
2025-04-18 06:40:28 +08:00
|
|
|
const char* sizeStr = xmlFallback->Attribute("size");
|
2025-03-05 00:27:39 +08:00
|
|
|
if (sizeStr)
|
|
|
|
size = std::strtol(sizeStr, nullptr, 10);
|
|
|
|
|
|
|
|
result->setFallback(fallback, size);
|
2017-02-25 04:56:57 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-02-17 22:43:25 +08:00
|
|
|
// static
|
|
|
|
SkinTheme* SkinTheme::instance()
|
|
|
|
{
|
2022-02-19 06:01:46 +08:00
|
|
|
if (auto mgr = ui::Manager::getDefault())
|
|
|
|
return SkinTheme::get(mgr);
|
|
|
|
else
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
SkinTheme* SkinTheme::get(const ui::Widget* widget)
|
|
|
|
{
|
|
|
|
ASSERT(widget);
|
|
|
|
ASSERT(widget->theme());
|
|
|
|
ASSERT(dynamic_cast<SkinTheme*>(widget->theme()));
|
|
|
|
return static_cast<SkinTheme*>(widget->theme());
|
2015-02-17 22:43:25 +08:00
|
|
|
}
|
|
|
|
|
2025-03-05 00:27:39 +08:00
|
|
|
SkinTheme::SkinTheme()
|
|
|
|
: m_fonts(m_fontMgr)
|
|
|
|
, m_sheet(nullptr)
|
|
|
|
, m_preferredScreenScaling(-1)
|
|
|
|
, m_preferredUIScaling(-1)
|
2009-11-22 04:02:31 +08:00
|
|
|
{
|
2021-07-09 21:21:16 +08:00
|
|
|
m_standardCursors.fill(nullptr);
|
2010-04-24 06:03:09 +08:00
|
|
|
}
|
|
|
|
|
2011-01-22 04:29:45 +08:00
|
|
|
SkinTheme::~SkinTheme()
|
2010-04-24 06:03:09 +08:00
|
|
|
{
|
2012-08-11 10:14:54 +08:00
|
|
|
// Delete all cursors.
|
2017-09-29 02:27:27 +08:00
|
|
|
for (auto& it : m_cursors)
|
2017-04-07 05:41:18 +08:00
|
|
|
delete it.second; // Delete cursor
|
2010-04-24 06:03:09 +08:00
|
|
|
|
2023-04-13 04:28:12 +08:00
|
|
|
m_unscaledSheet.reset();
|
2020-07-08 06:06:48 +08:00
|
|
|
m_sheet.reset();
|
2014-05-03 10:07:11 +08:00
|
|
|
m_parts_by_id.clear();
|
2011-04-02 22:45:43 +08:00
|
|
|
|
2021-06-08 01:19:54 +08:00
|
|
|
// Delete all styles.
|
|
|
|
for (auto style : m_styles)
|
|
|
|
delete style.second;
|
|
|
|
m_styles.clear();
|
2010-04-24 06:03:09 +08:00
|
|
|
}
|
|
|
|
|
2017-08-15 21:39:06 +08:00
|
|
|
void SkinTheme::onRegenerateTheme()
|
2010-04-24 06:03:09 +08:00
|
|
|
{
|
2015-09-19 00:05:52 +08:00
|
|
|
Preferences& pref = Preferences::instance();
|
2023-03-01 01:28:09 +08:00
|
|
|
BackwardCompatibility backward;
|
2015-09-19 00:05:52 +08:00
|
|
|
|
|
|
|
// First we load the skin from default theme, which is more proper
|
|
|
|
// to have every single needed skin part/color/dimension.
|
2023-03-01 01:28:09 +08:00
|
|
|
loadAll(pref.theme.selected.defaultValue(), &backward);
|
2015-09-19 00:05:52 +08:00
|
|
|
|
|
|
|
// Then we load the selected theme to redefine default theme parts.
|
2016-10-27 21:44:31 +08:00
|
|
|
if (pref.theme.selected.defaultValue() != pref.theme.selected()) {
|
|
|
|
try {
|
2023-03-01 01:28:09 +08:00
|
|
|
backward.copyingStyles();
|
2020-05-02 10:31:10 +08:00
|
|
|
loadAll(pref.theme.selected(), &backward);
|
2016-10-27 21:44:31 +08:00
|
|
|
}
|
|
|
|
catch (const std::exception& e) {
|
2017-03-09 06:25:03 +08:00
|
|
|
LOG("THEME: Error loading user-theme: %s\n", e.what());
|
2016-10-27 21:44:31 +08:00
|
|
|
|
2017-06-30 02:26:18 +08:00
|
|
|
// Load default theme again
|
|
|
|
loadAll(pref.theme.selected.defaultValue());
|
|
|
|
|
2016-12-12 20:48:58 +08:00
|
|
|
if (ui::get_theme())
|
2016-10-27 21:44:31 +08:00
|
|
|
Console::showException(e);
|
|
|
|
|
|
|
|
// We can continue, as we've already loaded the default theme
|
2016-10-28 22:25:46 +08:00
|
|
|
// anyway. Here we restore the setting to its default value.
|
|
|
|
pref.theme.selected(pref.theme.selected.defaultValue());
|
2016-10-27 21:44:31 +08:00
|
|
|
}
|
|
|
|
}
|
2015-09-19 00:05:52 +08:00
|
|
|
}
|
|
|
|
|
2017-02-25 04:56:57 +08:00
|
|
|
void SkinTheme::loadFontData()
|
|
|
|
{
|
2017-03-09 06:25:03 +08:00
|
|
|
LOG("THEME: Loading fonts\n");
|
2017-02-25 04:56:57 +08:00
|
|
|
|
2023-04-13 04:28:12 +08:00
|
|
|
std::string fontsFilename("fonts/fonts.xml");
|
2017-02-25 04:56:57 +08:00
|
|
|
|
|
|
|
ResourceFinder rf;
|
2023-04-13 04:28:12 +08:00
|
|
|
rf.includeDataDir(fontsFilename.c_str());
|
2017-02-25 04:56:57 +08:00
|
|
|
if (!rf.findFirst())
|
2023-04-13 04:28:12 +08:00
|
|
|
throw base::Exception("File %s not found", fontsFilename.c_str());
|
2017-02-25 04:56:57 +08:00
|
|
|
|
2024-04-23 05:28:03 +08:00
|
|
|
XMLDocumentRef doc = open_xml(rf.filename());
|
|
|
|
XMLHandle handle(doc.get());
|
2017-02-25 04:56:57 +08:00
|
|
|
|
2024-04-23 05:28:03 +08:00
|
|
|
XMLElement* xmlFont = handle.FirstChildElement("fonts").FirstChildElement("font").ToElement();
|
2017-02-25 04:56:57 +08:00
|
|
|
while (xmlFont) {
|
2025-03-05 00:27:39 +08:00
|
|
|
load_font(xmlFont, rf.filename());
|
2017-02-25 04:56:57 +08:00
|
|
|
xmlFont = xmlFont->NextSiblingElement();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-02 10:31:10 +08:00
|
|
|
void SkinTheme::loadAll(const std::string& themeId, BackwardCompatibility* backward)
|
2015-09-19 00:05:52 +08:00
|
|
|
{
|
2017-06-11 02:02:39 +08:00
|
|
|
LOG("THEME: Loading theme %s\n", themeId.c_str());
|
2016-10-27 21:44:31 +08:00
|
|
|
|
2025-03-05 00:27:39 +08:00
|
|
|
if (Fonts::instance()->isEmpty())
|
2017-02-25 04:56:57 +08:00
|
|
|
loadFontData();
|
|
|
|
|
2017-06-13 22:51:49 +08:00
|
|
|
m_path = findThemePath(themeId);
|
2017-06-11 02:02:39 +08:00
|
|
|
if (m_path.empty())
|
|
|
|
throw base::Exception("Theme %s not found", themeId.c_str());
|
|
|
|
|
|
|
|
loadSheet();
|
2020-05-02 10:31:10 +08:00
|
|
|
loadXml(backward);
|
2015-09-19 00:05:52 +08:00
|
|
|
}
|
|
|
|
|
2017-06-11 02:02:39 +08:00
|
|
|
void SkinTheme::loadSheet()
|
2015-09-19 00:05:52 +08:00
|
|
|
{
|
2024-03-06 05:50:24 +08:00
|
|
|
os::SystemRef system = os::System::instance();
|
|
|
|
|
2010-03-07 22:03:47 +08:00
|
|
|
// Load the skin sheet
|
2017-06-11 02:02:39 +08:00
|
|
|
std::string sheet_filename(base::join_path(m_path, "sheet.png"));
|
2020-07-08 06:06:48 +08:00
|
|
|
os::SurfaceRef newSheet;
|
2014-06-23 05:53:14 +08:00
|
|
|
try {
|
2024-03-06 05:50:24 +08:00
|
|
|
newSheet = os::System::instance()->loadRgbaSurface(sheet_filename.c_str());
|
2014-06-23 05:53:14 +08:00
|
|
|
}
|
|
|
|
catch (...) {
|
2018-06-26 01:05:57 +08:00
|
|
|
// Ignore the error, newSheet is nullptr and we will throw our own
|
|
|
|
// exception.
|
2014-06-23 05:53:14 +08:00
|
|
|
}
|
2018-06-26 01:05:57 +08:00
|
|
|
if (!newSheet)
|
|
|
|
throw base::Exception("Error loading %s file", sheet_filename.c_str());
|
2017-06-30 02:26:18 +08:00
|
|
|
|
2025-03-04 03:42:03 +08:00
|
|
|
// Set the unscaled and scaled version of the sprite sheet.
|
|
|
|
m_unscaledSheet = newSheet;
|
|
|
|
m_unscaledSheet->setImmutable();
|
|
|
|
m_sheet = newSheet->applyScale(guiscale());
|
2021-11-09 22:07:18 +08:00
|
|
|
m_sheet->setImmutable();
|
2017-06-30 02:26:18 +08:00
|
|
|
|
2017-09-29 02:27:27 +08:00
|
|
|
// Reset sprite sheet and font of all layer styles (to avoid
|
2024-02-21 02:40:17 +08:00
|
|
|
// dangling pointers to os::Surface or text::Font).
|
2017-09-29 02:27:27 +08:00
|
|
|
for (auto& it : m_styles) {
|
|
|
|
for (auto& layer : it.second->layers()) {
|
|
|
|
layer.setIcon(nullptr);
|
|
|
|
layer.setSpriteSheet(nullptr);
|
2017-06-30 02:26:18 +08:00
|
|
|
}
|
2017-09-29 02:27:27 +08:00
|
|
|
it.second->setFont(nullptr);
|
2017-06-30 02:26:18 +08:00
|
|
|
}
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
|
2020-05-02 10:31:10 +08:00
|
|
|
void SkinTheme::loadXml(BackwardCompatibility* backward)
|
2009-11-22 04:02:31 +08:00
|
|
|
{
|
2025-03-05 00:27:39 +08:00
|
|
|
Fonts* fonts = Fonts::instance();
|
2017-04-15 19:13:20 +08:00
|
|
|
const int scale = guiscale();
|
|
|
|
|
2010-03-25 08:35:44 +08:00
|
|
|
// Load the skin XML
|
2017-06-11 02:02:39 +08:00
|
|
|
std::string xml_filename(base::join_path(m_path, "theme.xml"));
|
2013-01-07 01:45:43 +08:00
|
|
|
|
2024-04-23 05:28:03 +08:00
|
|
|
XMLDocumentRef doc = open_xml(xml_filename);
|
|
|
|
XMLHandle handle(doc.get());
|
2013-12-11 11:34:16 +08:00
|
|
|
|
2017-08-19 06:23:23 +08:00
|
|
|
// Load Preferred scaling
|
|
|
|
m_preferredScreenScaling = -1;
|
|
|
|
m_preferredUIScaling = -1;
|
|
|
|
{
|
2024-04-23 05:28:03 +08:00
|
|
|
XMLElement* xmlTheme = handle.FirstChildElement("theme").ToElement();
|
2017-08-19 06:23:23 +08:00
|
|
|
if (xmlTheme) {
|
|
|
|
const char* screenScaling = xmlTheme->Attribute("screenscaling");
|
|
|
|
const char* uiScaling = xmlTheme->Attribute("uiscaling");
|
|
|
|
if (screenScaling)
|
|
|
|
m_preferredScreenScaling = std::strtol(screenScaling, nullptr, 10);
|
|
|
|
if (uiScaling)
|
|
|
|
m_preferredUIScaling = std::strtol(uiScaling, nullptr, 10);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-25 04:56:57 +08:00
|
|
|
// Load fonts
|
|
|
|
{
|
2024-04-23 05:28:03 +08:00
|
|
|
XMLElement* xmlFont = handle.FirstChildElement("theme")
|
|
|
|
.FirstChildElement("fonts")
|
|
|
|
.FirstChildElement("font")
|
|
|
|
.ToElement();
|
2017-02-25 04:56:57 +08:00
|
|
|
while (xmlFont) {
|
|
|
|
const char* idStr = xmlFont->Attribute("id");
|
2025-03-05 00:27:39 +08:00
|
|
|
FontData* fontData = load_font(xmlFont, xml_filename);
|
2017-02-25 04:56:57 +08:00
|
|
|
if (idStr && fontData) {
|
|
|
|
std::string id(idStr);
|
2020-06-18 04:05:43 +08:00
|
|
|
LOG(VERBOSE, "THEME: Loading theme font %s\n", idStr);
|
2017-02-25 04:56:57 +08:00
|
|
|
|
2025-04-18 06:40:28 +08:00
|
|
|
float size = 0.0f;
|
|
|
|
if (const char* sizeStr = xmlFont->Attribute("size"))
|
|
|
|
size = std::strtof(sizeStr, nullptr);
|
2025-06-14 04:48:48 +08:00
|
|
|
if (size == 0.0f && fontData->defaultSize() != 0.0f)
|
2025-04-18 06:40:28 +08:00
|
|
|
size = fontData->defaultSize();
|
2017-02-25 04:56:57 +08:00
|
|
|
|
2022-12-03 02:13:45 +08:00
|
|
|
const char* mnemonicsStr = xmlFont->Attribute("mnemonics");
|
|
|
|
bool mnemonics = mnemonicsStr ? (std::string(mnemonicsStr) != "off") : true;
|
|
|
|
|
2025-03-19 00:43:04 +08:00
|
|
|
text::FontRef font = fontData->getFont(m_fontMgr, size * ui::guiscale());
|
|
|
|
|
2025-06-11 09:02:12 +08:00
|
|
|
// No font?
|
|
|
|
if (font == nullptr) {
|
|
|
|
LOG(ERROR, "THEME: Error loading font for theme %s\n", idStr);
|
|
|
|
xmlFont = xmlFont->NextSiblingElement();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2025-06-14 04:48:48 +08:00
|
|
|
if (size == 0.0f) {
|
|
|
|
// SpriteSheetFonts have a default preferred size.
|
|
|
|
if (font->defaultSize() > 0.0f) {
|
|
|
|
size = font->defaultSize();
|
|
|
|
}
|
|
|
|
// For some user extensions, we need to specify at least a
|
|
|
|
// default size of 10 for TTF theme fonts.
|
|
|
|
else {
|
|
|
|
size = 10.0f;
|
|
|
|
}
|
2025-03-19 00:43:04 +08:00
|
|
|
font = fontData->getFont(m_fontMgr, size * ui::guiscale());
|
|
|
|
}
|
|
|
|
|
2022-12-03 02:13:45 +08:00
|
|
|
m_themeFonts[idStr] = ThemeFont(font, mnemonics);
|
2017-03-09 05:53:36 +08:00
|
|
|
|
2023-04-13 04:28:12 +08:00
|
|
|
// Store a unscaled version for using when ui scaling is not desired (i.e. in a Canvas
|
|
|
|
// widget with autoScaling enabled).
|
2025-03-19 00:43:04 +08:00
|
|
|
m_unscaledFonts[font.get()] = fontData->getFont(m_fontMgr, size);
|
2023-04-13 04:28:12 +08:00
|
|
|
|
2025-04-18 06:40:28 +08:00
|
|
|
if (id == "default") {
|
2017-03-09 05:53:36 +08:00
|
|
|
m_defaultFont = font;
|
2025-04-18 06:40:28 +08:00
|
|
|
m_defaultFontInfo = FontInfo(fontData, size);
|
|
|
|
}
|
|
|
|
else if (id == "mini") {
|
2017-03-09 05:53:36 +08:00
|
|
|
m_miniFont = font;
|
2025-04-18 06:40:28 +08:00
|
|
|
m_miniFontInfo = FontInfo(fontData, size);
|
|
|
|
}
|
2017-02-25 04:56:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
xmlFont = xmlFont->NextSiblingElement();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// No available font to run the program
|
2025-04-18 06:40:28 +08:00
|
|
|
if (!m_defaultFont) {
|
|
|
|
throw base::Exception(
|
|
|
|
fmt::format("{}: No valid default font element found (<font id=\"default\" ... />)",
|
|
|
|
xml_filename));
|
|
|
|
}
|
|
|
|
if (!m_miniFont) {
|
2017-02-25 04:56:57 +08:00
|
|
|
m_miniFont = m_defaultFont;
|
2025-04-18 06:40:28 +08:00
|
|
|
m_miniFontInfo = m_defaultFontInfo;
|
|
|
|
}
|
2025-02-15 23:54:42 +08:00
|
|
|
|
2025-02-13 04:31:27 +08:00
|
|
|
// Overwrite theme fonts by user defined fonts.
|
|
|
|
Preferences& pref = Preferences::instance();
|
|
|
|
if (!pref.theme.font().empty()) {
|
|
|
|
auto fi = base::convert_to<FontInfo>(pref.theme.font());
|
2025-04-18 06:40:28 +08:00
|
|
|
if (auto f = fonts->fontFromInfo(fi)) {
|
2025-02-13 04:31:27 +08:00
|
|
|
m_defaultFont = f;
|
2025-04-18 06:40:28 +08:00
|
|
|
m_defaultFontInfo = fi;
|
|
|
|
}
|
2025-02-13 04:31:27 +08:00
|
|
|
}
|
|
|
|
if (!pref.theme.miniFont().empty()) {
|
|
|
|
auto fi = base::convert_to<FontInfo>(pref.theme.miniFont());
|
2025-04-18 06:40:28 +08:00
|
|
|
if (auto f = fonts->fontFromInfo(fi)) {
|
2025-02-13 04:31:27 +08:00
|
|
|
m_miniFont = f;
|
2025-04-18 06:40:28 +08:00
|
|
|
m_miniFontInfo = fi;
|
|
|
|
}
|
2025-02-13 04:31:27 +08:00
|
|
|
}
|
|
|
|
|
2015-02-17 23:22:46 +08:00
|
|
|
// Load dimension
|
|
|
|
{
|
2024-04-23 05:28:03 +08:00
|
|
|
XMLElement* xmlDim = handle.FirstChildElement("theme")
|
|
|
|
.FirstChildElement("dimensions")
|
|
|
|
.FirstChildElement("dim")
|
|
|
|
.ToElement();
|
2015-02-17 23:22:46 +08:00
|
|
|
while (xmlDim) {
|
|
|
|
std::string id = xmlDim->Attribute("id");
|
|
|
|
uint32_t value = strtol(xmlDim->Attribute("value"), NULL, 10);
|
|
|
|
|
2020-06-18 04:05:43 +08:00
|
|
|
LOG(VERBOSE, "THEME: Loading dimension %s\n", id.c_str());
|
2015-02-17 23:22:46 +08:00
|
|
|
|
|
|
|
m_dimensions_by_id[id] = value;
|
|
|
|
xmlDim = xmlDim->NextSiblingElement();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-11 11:34:16 +08:00
|
|
|
// Load colors
|
|
|
|
{
|
2024-04-23 05:28:03 +08:00
|
|
|
XMLElement* xmlColor = handle.FirstChildElement("theme")
|
|
|
|
.FirstChildElement("colors")
|
|
|
|
.FirstChildElement("color")
|
|
|
|
.ToElement();
|
2013-12-11 11:34:16 +08:00
|
|
|
while (xmlColor) {
|
|
|
|
std::string id = xmlColor->Attribute("id");
|
|
|
|
uint32_t value = strtol(xmlColor->Attribute("value") + 1, NULL, 16);
|
2014-06-29 03:10:39 +08:00
|
|
|
gfx::Color color = gfx::rgba((value & 0xff0000) >> 16, (value & 0xff00) >> 8, (value & 0xff));
|
2013-12-11 11:34:16 +08:00
|
|
|
|
2020-06-18 04:05:43 +08:00
|
|
|
LOG(VERBOSE, "THEME: Loading color %s\n", id.c_str());
|
2013-12-11 11:34:16 +08:00
|
|
|
|
|
|
|
m_colors_by_id[id] = color;
|
|
|
|
xmlColor = xmlColor->NextSiblingElement();
|
|
|
|
}
|
|
|
|
}
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2013-12-11 11:34:16 +08:00
|
|
|
// Load parts
|
|
|
|
{
|
2024-04-23 05:28:03 +08:00
|
|
|
XMLElement* xmlPart = handle.FirstChildElement("theme")
|
|
|
|
.FirstChildElement("parts")
|
|
|
|
.FirstChildElement("part")
|
|
|
|
.ToElement();
|
2013-12-11 11:34:16 +08:00
|
|
|
while (xmlPart) {
|
|
|
|
// Get the tool-icon rectangle
|
|
|
|
const char* part_id = xmlPart->Attribute("id");
|
2017-04-15 19:13:20 +08:00
|
|
|
int x = scale * strtol(xmlPart->Attribute("x"), nullptr, 10);
|
|
|
|
int y = scale * strtol(xmlPart->Attribute("y"), nullptr, 10);
|
|
|
|
int w = (xmlPart->Attribute("w") ? scale * strtol(xmlPart->Attribute("w"), nullptr, 10) : 0);
|
|
|
|
int h = (xmlPart->Attribute("h") ? scale * strtol(xmlPart->Attribute("h"), nullptr, 10) : 0);
|
2013-12-11 11:34:16 +08:00
|
|
|
|
2020-06-18 04:05:43 +08:00
|
|
|
LOG(VERBOSE, "THEME: Loading part %s\n", part_id);
|
2013-12-11 11:34:16 +08:00
|
|
|
|
|
|
|
SkinPartPtr part = m_parts_by_id[part_id];
|
2015-04-03 07:42:43 +08:00
|
|
|
if (!part)
|
2013-12-11 11:34:16 +08:00
|
|
|
part = m_parts_by_id[part_id] = SkinPartPtr(new SkinPart);
|
|
|
|
|
2023-04-13 04:28:12 +08:00
|
|
|
SkinPartPtr unscaledPart = m_unscaledParts_by_id[part_id];
|
|
|
|
if (!unscaledPart)
|
|
|
|
unscaledPart = m_unscaledParts_by_id[part_id] = SkinPartPtr(new SkinPart);
|
|
|
|
|
2013-12-11 11:34:16 +08:00
|
|
|
if (w > 0 && h > 0) {
|
2017-02-10 03:51:52 +08:00
|
|
|
part->setSpriteBounds(gfx::Rect(x, y, w, h));
|
2020-07-08 06:06:48 +08:00
|
|
|
part->setBitmap(0, sliceSheet(part->bitmapRef(0), gfx::Rect(x, y, w, h)));
|
2023-04-13 04:28:12 +08:00
|
|
|
unscaledPart->setSpriteBounds(part->spriteBounds() / scale);
|
|
|
|
unscaledPart->setBitmap(
|
|
|
|
0,
|
|
|
|
sliceUnscaledSheet(unscaledPart->bitmapRef(0), unscaledPart->spriteBounds()));
|
2013-12-11 11:34:16 +08:00
|
|
|
}
|
|
|
|
else if (xmlPart->Attribute("w1")) { // 3x3-1 part (NW, N, NE, E, SE, S, SW, W)
|
2017-04-15 19:13:20 +08:00
|
|
|
int w1 = scale * strtol(xmlPart->Attribute("w1"), nullptr, 10);
|
|
|
|
int w2 = scale * strtol(xmlPart->Attribute("w2"), nullptr, 10);
|
|
|
|
int w3 = scale * strtol(xmlPart->Attribute("w3"), nullptr, 10);
|
|
|
|
int h1 = scale * strtol(xmlPart->Attribute("h1"), nullptr, 10);
|
|
|
|
int h2 = scale * strtol(xmlPart->Attribute("h2"), nullptr, 10);
|
|
|
|
int h3 = scale * strtol(xmlPart->Attribute("h3"), nullptr, 10);
|
2024-12-17 01:52:19 +08:00
|
|
|
|
2017-02-10 03:51:52 +08:00
|
|
|
part->setSpriteBounds(gfx::Rect(x, y, w1 + w2 + w3, h1 + h2 + h3));
|
|
|
|
part->setSlicesBounds(gfx::Rect(w1, h1, w2, h2));
|
|
|
|
|
2020-07-08 06:06:48 +08:00
|
|
|
part->setBitmap(0, sliceSheet(part->bitmapRef(0), gfx::Rect(x, y, w1, h1))); // NW
|
|
|
|
part->setBitmap(1, sliceSheet(part->bitmapRef(1), gfx::Rect(x + w1, y, w2, h1))); // N
|
|
|
|
part->setBitmap(2, sliceSheet(part->bitmapRef(2), gfx::Rect(x + w1 + w2, y, w3, h1))); // NE
|
|
|
|
part->setBitmap(
|
|
|
|
3,
|
|
|
|
sliceSheet(part->bitmapRef(3), gfx::Rect(x + w1 + w2, y + h1, w3, h2))); // E
|
|
|
|
part->setBitmap(
|
|
|
|
4,
|
|
|
|
sliceSheet(part->bitmapRef(4), gfx::Rect(x + w1 + w2, y + h1 + h2, w3, h3))); // SE
|
|
|
|
part->setBitmap(
|
|
|
|
5,
|
|
|
|
sliceSheet(part->bitmapRef(5), gfx::Rect(x + w1, y + h1 + h2, w2, h3))); // S
|
|
|
|
part->setBitmap(6, sliceSheet(part->bitmapRef(6), gfx::Rect(x, y + h1 + h2, w1, h3))); // SW
|
|
|
|
part->setBitmap(7, sliceSheet(part->bitmapRef(7), gfx::Rect(x, y + h1, w1, h2))); // W
|
2024-12-17 01:52:19 +08:00
|
|
|
|
2023-04-13 04:28:12 +08:00
|
|
|
unscaledPart->setSpriteBounds(part->spriteBounds() / scale);
|
|
|
|
unscaledPart->setSlicesBounds(part->slicesBounds() / scale);
|
2024-12-17 01:52:19 +08:00
|
|
|
|
2023-04-13 04:28:12 +08:00
|
|
|
unscaledPart->setBitmap(
|
|
|
|
0,
|
|
|
|
sliceUnscaledSheet(unscaledPart->bitmapRef(0), gfx::Rect(x, y, w1, h1) / scale));
|
|
|
|
unscaledPart->setBitmap(
|
|
|
|
1,
|
|
|
|
sliceUnscaledSheet(unscaledPart->bitmapRef(1), gfx::Rect(x + w1, y, w2, h1) / scale));
|
|
|
|
unscaledPart->setBitmap(2,
|
|
|
|
sliceUnscaledSheet(unscaledPart->bitmapRef(2),
|
|
|
|
gfx::Rect(x + w1 + w2, y, w3, h1) / scale));
|
|
|
|
unscaledPart->setBitmap(3,
|
|
|
|
sliceUnscaledSheet(unscaledPart->bitmapRef(3),
|
|
|
|
gfx::Rect(x + w1 + w2, y + h1, w3, h2) / scale));
|
|
|
|
unscaledPart->setBitmap(
|
|
|
|
4,
|
|
|
|
sliceUnscaledSheet(unscaledPart->bitmapRef(4),
|
|
|
|
gfx::Rect(x + w1 + w2, y + h1 + h2, w3, h3) / scale));
|
|
|
|
unscaledPart->setBitmap(5,
|
|
|
|
sliceUnscaledSheet(unscaledPart->bitmapRef(5),
|
|
|
|
gfx::Rect(x + w1, y + h1 + h2, w2, h3) / scale));
|
|
|
|
unscaledPart->setBitmap(6,
|
|
|
|
sliceUnscaledSheet(unscaledPart->bitmapRef(6),
|
|
|
|
gfx::Rect(x, y + h1 + h2, w1, h3) / scale));
|
|
|
|
unscaledPart->setBitmap(
|
|
|
|
7,
|
|
|
|
sliceUnscaledSheet(unscaledPart->bitmapRef(7), gfx::Rect(x, y + h1, w1, h2) / scale));
|
2013-12-11 11:34:16 +08:00
|
|
|
}
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2017-03-15 06:00:24 +08:00
|
|
|
// Is it a mouse cursor?
|
|
|
|
if (std::strncmp(part_id, "cursor_", 7) == 0) {
|
|
|
|
std::string cursorName = std::string(part_id).substr(7);
|
2017-04-15 19:13:20 +08:00
|
|
|
int focusx = scale * std::strtol(xmlPart->Attribute("focusx"), NULL, 10);
|
|
|
|
int focusy = scale * std::strtol(xmlPart->Attribute("focusy"), NULL, 10);
|
2017-03-15 06:00:24 +08:00
|
|
|
|
2020-06-18 04:05:43 +08:00
|
|
|
LOG(VERBOSE, "THEME: Loading cursor '%s'\n", cursorName.c_str());
|
2017-03-15 06:00:24 +08:00
|
|
|
|
2017-04-07 05:41:18 +08:00
|
|
|
auto it = m_cursors.find(cursorName);
|
|
|
|
if (it != m_cursors.end() && it->second != nullptr) {
|
|
|
|
delete it->second;
|
|
|
|
it->second = nullptr;
|
|
|
|
}
|
2017-03-15 06:00:24 +08:00
|
|
|
|
2020-07-08 06:06:48 +08:00
|
|
|
os::SurfaceRef slice = sliceSheet(nullptr, gfx::Rect(x, y, w, h));
|
|
|
|
Cursor* cursor = new Cursor(slice, gfx::Point(focusx, focusy));
|
2017-04-07 05:41:18 +08:00
|
|
|
m_cursors[cursorName] = cursor;
|
2017-03-15 06:00:24 +08:00
|
|
|
|
2017-04-07 05:41:18 +08:00
|
|
|
for (int c = 0; c < kCursorTypes; ++c) {
|
|
|
|
if (cursorName == g_cursor_names[c]) {
|
|
|
|
m_standardCursors[c] = cursor;
|
|
|
|
break;
|
|
|
|
}
|
2017-03-15 06:00:24 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-11 11:34:16 +08:00
|
|
|
xmlPart = xmlPart->NextSiblingElement();
|
|
|
|
}
|
|
|
|
}
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2017-03-14 05:13:38 +08:00
|
|
|
// Load styles
|
2017-02-14 05:34:23 +08:00
|
|
|
{
|
2024-04-23 05:28:03 +08:00
|
|
|
XMLElement* xmlStyle = handle.FirstChildElement("theme")
|
|
|
|
.FirstChildElement("styles")
|
|
|
|
.FirstChildElement("style")
|
|
|
|
.ToElement();
|
2017-06-30 02:26:18 +08:00
|
|
|
|
|
|
|
if (!xmlStyle) // Without styles?
|
|
|
|
throw base::Exception("There are no styles");
|
|
|
|
|
2023-03-01 01:28:09 +08:00
|
|
|
if (backward) {
|
|
|
|
backward->removeExistentStyles(xmlStyle);
|
|
|
|
backward->copyMissingStyles(xmlStyle->Parent());
|
|
|
|
}
|
|
|
|
|
2017-02-14 05:34:23 +08:00
|
|
|
while (xmlStyle) {
|
|
|
|
const char* style_id = xmlStyle->Attribute("id");
|
|
|
|
if (!style_id) {
|
|
|
|
throw base::Exception("<style> without 'id' attribute in '%s'\n", xml_filename.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* extends_id = xmlStyle->Attribute("extends");
|
|
|
|
const ui::Style* base = nullptr;
|
|
|
|
if (extends_id)
|
|
|
|
base = m_styles[extends_id];
|
|
|
|
|
2020-05-02 10:31:10 +08:00
|
|
|
if (backward)
|
2023-03-01 01:28:09 +08:00
|
|
|
backward->onStyle(xmlStyle);
|
2020-05-02 10:31:10 +08:00
|
|
|
|
2017-02-14 05:34:23 +08:00
|
|
|
ui::Style* style = m_styles[style_id];
|
|
|
|
if (!style) {
|
|
|
|
m_styles[style_id] = style = new ui::Style(base);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
*style = ui::Style(base);
|
|
|
|
}
|
2017-09-29 02:27:27 +08:00
|
|
|
style->setId(style_id);
|
2017-02-14 05:34:23 +08:00
|
|
|
|
2017-02-18 01:18:47 +08:00
|
|
|
// Margin
|
|
|
|
{
|
|
|
|
const char* m = xmlStyle->Attribute("margin");
|
|
|
|
const char* l = xmlStyle->Attribute("margin-left");
|
|
|
|
const char* t = xmlStyle->Attribute("margin-top");
|
|
|
|
const char* r = xmlStyle->Attribute("margin-right");
|
|
|
|
const char* b = xmlStyle->Attribute("margin-bottom");
|
2025-03-10 09:01:55 +08:00
|
|
|
gfx::Border margin = style->rawMargin();
|
2017-04-15 19:13:20 +08:00
|
|
|
if (m || l)
|
|
|
|
margin.left(scale * std::strtol(l ? l : m, nullptr, 10));
|
|
|
|
if (m || t)
|
|
|
|
margin.top(scale * std::strtol(t ? t : m, nullptr, 10));
|
|
|
|
if (m || r)
|
|
|
|
margin.right(scale * std::strtol(r ? r : m, nullptr, 10));
|
|
|
|
if (m || b)
|
|
|
|
margin.bottom(scale * std::strtol(b ? b : m, nullptr, 10));
|
|
|
|
style->setMargin(margin);
|
2017-02-18 01:18:47 +08:00
|
|
|
}
|
|
|
|
|
2017-02-14 05:34:23 +08:00
|
|
|
// Border
|
|
|
|
{
|
|
|
|
const char* m = xmlStyle->Attribute("border");
|
|
|
|
const char* l = xmlStyle->Attribute("border-left");
|
|
|
|
const char* t = xmlStyle->Attribute("border-top");
|
|
|
|
const char* r = xmlStyle->Attribute("border-right");
|
|
|
|
const char* b = xmlStyle->Attribute("border-bottom");
|
2025-03-10 09:01:55 +08:00
|
|
|
gfx::Border border = style->rawBorder();
|
2017-04-15 19:13:20 +08:00
|
|
|
if (m || l)
|
|
|
|
border.left(scale * std::strtol(l ? l : m, nullptr, 10));
|
|
|
|
if (m || t)
|
|
|
|
border.top(scale * std::strtol(t ? t : m, nullptr, 10));
|
|
|
|
if (m || r)
|
|
|
|
border.right(scale * std::strtol(r ? r : m, nullptr, 10));
|
|
|
|
if (m || b)
|
|
|
|
border.bottom(scale * std::strtol(b ? b : m, nullptr, 10));
|
|
|
|
style->setBorder(border);
|
2017-02-14 05:34:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Padding
|
|
|
|
{
|
|
|
|
const char* m = xmlStyle->Attribute("padding");
|
|
|
|
const char* l = xmlStyle->Attribute("padding-left");
|
|
|
|
const char* t = xmlStyle->Attribute("padding-top");
|
|
|
|
const char* r = xmlStyle->Attribute("padding-right");
|
|
|
|
const char* b = xmlStyle->Attribute("padding-bottom");
|
2025-03-10 09:01:55 +08:00
|
|
|
gfx::Border padding = style->rawPadding();
|
2017-04-15 19:13:20 +08:00
|
|
|
if (m || l)
|
|
|
|
padding.left(scale * std::strtol(l ? l : m, nullptr, 10));
|
|
|
|
if (m || t)
|
|
|
|
padding.top(scale * std::strtol(t ? t : m, nullptr, 10));
|
|
|
|
if (m || r)
|
|
|
|
padding.right(scale * std::strtol(r ? r : m, nullptr, 10));
|
|
|
|
if (m || b)
|
|
|
|
padding.bottom(scale * std::strtol(b ? b : m, nullptr, 10));
|
|
|
|
style->setPadding(padding);
|
2017-02-14 05:34:23 +08:00
|
|
|
}
|
|
|
|
|
2022-12-08 02:41:33 +08:00
|
|
|
// Size
|
|
|
|
{
|
|
|
|
const char* width = xmlStyle->Attribute("width");
|
|
|
|
const char* height = xmlStyle->Attribute("height");
|
|
|
|
const char* minwidth = xmlStyle->Attribute("minwidth");
|
|
|
|
const char* minheight = xmlStyle->Attribute("minheight");
|
|
|
|
const char* maxwidth = xmlStyle->Attribute("maxwidth");
|
|
|
|
const char* maxheight = xmlStyle->Attribute("maxheight");
|
|
|
|
gfx::Size minSize = style->minSize();
|
|
|
|
gfx::Size maxSize = style->maxSize();
|
|
|
|
if (width) {
|
|
|
|
if (!minwidth)
|
|
|
|
minwidth = width;
|
|
|
|
if (!maxwidth)
|
|
|
|
maxwidth = width;
|
|
|
|
}
|
|
|
|
if (height) {
|
|
|
|
if (!minheight)
|
|
|
|
minheight = height;
|
|
|
|
if (!maxheight)
|
|
|
|
maxheight = height;
|
|
|
|
}
|
|
|
|
if (minwidth)
|
|
|
|
minSize.w = scale * std::strtol(minwidth, nullptr, 10);
|
|
|
|
if (minheight)
|
|
|
|
minSize.h = scale * std::strtol(minheight, nullptr, 10);
|
|
|
|
if (maxwidth)
|
|
|
|
maxSize.w = scale * std::strtol(maxwidth, nullptr, 10);
|
|
|
|
if (maxheight)
|
|
|
|
maxSize.h = scale * std::strtol(maxheight, nullptr, 10);
|
|
|
|
style->setMinSize(minSize);
|
|
|
|
style->setMaxSize(maxSize);
|
|
|
|
}
|
2022-12-13 00:54:16 +08:00
|
|
|
|
|
|
|
// Gap
|
|
|
|
{
|
|
|
|
const char* m = xmlStyle->Attribute("gap");
|
|
|
|
const char* r = xmlStyle->Attribute("gap-rows");
|
|
|
|
const char* c = xmlStyle->Attribute("gap-columns");
|
|
|
|
gfx::Size gap = style->gap();
|
|
|
|
if (m || c)
|
|
|
|
gap.w = scale * std::strtol(c ? c : m, nullptr, 10);
|
|
|
|
if (m || r)
|
|
|
|
gap.h = scale * std::strtol(r ? r : m, nullptr, 10);
|
|
|
|
style->setGap(gap);
|
|
|
|
}
|
|
|
|
|
2017-03-09 05:53:36 +08:00
|
|
|
// Font
|
|
|
|
{
|
|
|
|
const char* fontId = xmlStyle->Attribute("font");
|
|
|
|
if (fontId) {
|
2025-02-13 04:31:27 +08:00
|
|
|
// 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());
|
|
|
|
}
|
2022-12-03 02:13:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Override mnemonics value if it is defined for this style.
|
|
|
|
const char* mnemonicsStr = xmlStyle->Attribute("mnemonics");
|
|
|
|
if (mnemonicsStr) {
|
|
|
|
bool mnemonics = mnemonicsStr ? (std::string(mnemonicsStr) != "off") : true;
|
|
|
|
style->setMnemonics(mnemonics);
|
2017-03-09 05:53:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-23 05:28:03 +08:00
|
|
|
XMLElement* xmlLayer = xmlStyle->FirstChildElement();
|
2017-02-14 05:34:23 +08:00
|
|
|
while (xmlLayer) {
|
|
|
|
const std::string layerName = xmlLayer->Value();
|
|
|
|
|
2020-06-18 04:05:43 +08:00
|
|
|
LOG(VERBOSE, "THEME: Layer %s for %s\n", layerName.c_str(), style_id);
|
2017-02-14 05:34:23 +08:00
|
|
|
|
|
|
|
ui::Style::Layer layer;
|
|
|
|
|
|
|
|
// Layer type
|
|
|
|
if (layerName == "background") {
|
|
|
|
layer.setType(ui::Style::Layer::Type::kBackground);
|
|
|
|
}
|
2017-03-18 02:43:42 +08:00
|
|
|
else if (layerName == "background-border") {
|
|
|
|
layer.setType(ui::Style::Layer::Type::kBackgroundBorder);
|
|
|
|
}
|
2017-02-14 05:34:23 +08:00
|
|
|
else if (layerName == "border") {
|
|
|
|
layer.setType(ui::Style::Layer::Type::kBorder);
|
|
|
|
}
|
|
|
|
else if (layerName == "icon") {
|
|
|
|
layer.setType(ui::Style::Layer::Type::kIcon);
|
|
|
|
}
|
|
|
|
else if (layerName == "text") {
|
|
|
|
layer.setType(ui::Style::Layer::Type::kText);
|
|
|
|
}
|
|
|
|
else if (layerName == "newlayer") {
|
|
|
|
layer.setType(ui::Style::Layer::Type::kNewLayer);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse state condition
|
|
|
|
const char* stateValue = xmlLayer->Attribute("state");
|
|
|
|
if (stateValue) {
|
|
|
|
std::string state(stateValue);
|
|
|
|
int flags = 0;
|
|
|
|
if (state.find("disabled") != std::string::npos)
|
|
|
|
flags |= ui::Style::Layer::kDisabled;
|
|
|
|
if (state.find("selected") != std::string::npos)
|
|
|
|
flags |= ui::Style::Layer::kSelected;
|
|
|
|
if (state.find("focus") != std::string::npos)
|
|
|
|
flags |= ui::Style::Layer::kFocus;
|
|
|
|
if (state.find("mouse") != std::string::npos)
|
|
|
|
flags |= ui::Style::Layer::kMouse;
|
2022-12-01 03:46:40 +08:00
|
|
|
if (state.find("capture") != std::string::npos)
|
|
|
|
flags |= ui::Style::Layer::kCapture;
|
2017-02-14 05:34:23 +08:00
|
|
|
layer.setFlags(flags);
|
|
|
|
}
|
|
|
|
|
2017-02-15 01:55:45 +08:00
|
|
|
// Align
|
|
|
|
const char* alignValue = xmlLayer->Attribute("align");
|
|
|
|
if (alignValue) {
|
|
|
|
std::string alignString(alignValue);
|
|
|
|
int align = 0;
|
|
|
|
if (alignString.find("left") != std::string::npos)
|
|
|
|
align |= LEFT;
|
|
|
|
if (alignString.find("center") != std::string::npos)
|
|
|
|
align |= CENTER;
|
|
|
|
if (alignString.find("right") != std::string::npos)
|
|
|
|
align |= RIGHT;
|
|
|
|
if (alignString.find("top") != std::string::npos)
|
|
|
|
align |= TOP;
|
|
|
|
if (alignString.find("middle") != std::string::npos)
|
|
|
|
align |= MIDDLE;
|
|
|
|
if (alignString.find("bottom") != std::string::npos)
|
|
|
|
align |= BOTTOM;
|
|
|
|
if (alignString.find("wordwrap") != std::string::npos)
|
|
|
|
align |= WORDWRAP;
|
|
|
|
layer.setAlign(align);
|
|
|
|
}
|
|
|
|
|
2017-02-14 05:34:23 +08:00
|
|
|
// Color
|
|
|
|
const char* colorId = xmlLayer->Attribute("color");
|
|
|
|
if (colorId) {
|
2017-02-15 01:55:45 +08:00
|
|
|
auto it = m_colors_by_id.find(colorId);
|
|
|
|
if (it != m_colors_by_id.end())
|
|
|
|
layer.setColor(it->second);
|
2017-03-14 05:13:38 +08:00
|
|
|
else if (std::strcmp(colorId, "none") == 0) {
|
|
|
|
layer.setColor(gfx::ColorNone);
|
|
|
|
}
|
2017-02-15 01:55:45 +08:00
|
|
|
else {
|
|
|
|
throw base::Exception("Color <%s color='%s' ...> was not found in '%s'\n",
|
|
|
|
layerName.c_str(),
|
|
|
|
colorId,
|
|
|
|
xml_filename.c_str());
|
|
|
|
}
|
2017-02-14 05:34:23 +08:00
|
|
|
}
|
|
|
|
|
2017-02-18 05:00:10 +08:00
|
|
|
// Offset
|
|
|
|
const char* x = xmlLayer->Attribute("x");
|
|
|
|
const char* y = xmlLayer->Attribute("y");
|
|
|
|
if (x || y) {
|
|
|
|
gfx::Point offset(0, 0);
|
|
|
|
if (x)
|
|
|
|
offset.x = std::strtol(x, nullptr, 10);
|
|
|
|
if (y)
|
|
|
|
offset.y = std::strtol(y, nullptr, 10);
|
2017-04-15 19:13:20 +08:00
|
|
|
layer.setOffset(offset * scale);
|
2017-02-18 05:00:10 +08:00
|
|
|
}
|
|
|
|
|
2017-02-14 05:34:23 +08:00
|
|
|
// Sprite sheet
|
|
|
|
const char* partId = xmlLayer->Attribute("part");
|
|
|
|
if (partId) {
|
|
|
|
auto it = m_parts_by_id.find(partId);
|
|
|
|
if (it != m_parts_by_id.end()) {
|
|
|
|
SkinPartPtr part = it->second;
|
|
|
|
if (part) {
|
|
|
|
if (layer.type() == ui::Style::Layer::Type::kIcon)
|
2020-07-08 06:06:48 +08:00
|
|
|
layer.setIcon(AddRef(part->bitmap(0)));
|
2017-02-14 05:34:23 +08:00
|
|
|
else {
|
|
|
|
layer.setSpriteSheet(m_sheet);
|
|
|
|
layer.setSpriteBounds(part->spriteBounds());
|
|
|
|
layer.setSlicesBounds(part->slicesBounds());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-03-14 05:13:38 +08:00
|
|
|
else if (std::strcmp(partId, "none") == 0) {
|
|
|
|
layer.setIcon(nullptr);
|
|
|
|
layer.setSpriteSheet(nullptr);
|
|
|
|
layer.setSpriteBounds(gfx::Rect(0, 0, 0, 0));
|
|
|
|
layer.setSlicesBounds(gfx::Rect(0, 0, 0, 0));
|
|
|
|
}
|
2017-02-14 05:34:23 +08:00
|
|
|
else {
|
|
|
|
throw base::Exception("Part <%s part='%s' ...> was not found in '%s'\n",
|
|
|
|
layerName.c_str(),
|
|
|
|
partId,
|
|
|
|
xml_filename.c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (layer.type() != ui::Style::Layer::Type::kNone)
|
|
|
|
style->addLayer(layer);
|
|
|
|
|
|
|
|
xmlLayer = xmlLayer->NextSiblingElement();
|
|
|
|
}
|
|
|
|
|
|
|
|
xmlStyle = xmlStyle->NextSiblingElement();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-10 00:18:44 +08:00
|
|
|
ThemeFile<SkinTheme>::updateInternals();
|
2010-03-25 08:35:44 +08:00
|
|
|
}
|
|
|
|
|
2023-04-13 04:28:12 +08:00
|
|
|
static os::SurfaceRef sliceSheet(os::SurfaceRef sheet, os::SurfaceRef sur, const gfx::Rect& bounds)
|
2010-03-25 08:35:44 +08:00
|
|
|
{
|
2014-06-23 05:53:14 +08:00
|
|
|
if (sur && (sur->width() != bounds.w || sur->height() != bounds.h)) {
|
2017-08-12 02:53:04 +08:00
|
|
|
sur = nullptr;
|
2010-03-25 08:35:44 +08:00
|
|
|
}
|
|
|
|
|
2017-08-12 02:53:04 +08:00
|
|
|
if (!bounds.isEmpty()) {
|
|
|
|
if (!sur)
|
2024-03-06 05:50:24 +08:00
|
|
|
sur = os::System::instance()->makeRgbaSurface(bounds.w, bounds.h);
|
2014-06-23 05:53:14 +08:00
|
|
|
|
2023-04-13 04:28:12 +08:00
|
|
|
os::SurfaceLock lockSrc(sheet.get());
|
2020-07-08 06:06:48 +08:00
|
|
|
os::SurfaceLock lockDst(sur.get());
|
2023-04-13 04:28:12 +08:00
|
|
|
sheet->blitTo(sur.get(), bounds.x, bounds.y, 0, 0, bounds.w, bounds.h);
|
2021-11-19 00:37:34 +08:00
|
|
|
|
|
|
|
// The new surface is immutable because we're going to re-use the
|
|
|
|
// surface if we reload the theme.
|
|
|
|
//
|
|
|
|
// TODO Add sub-surfaces (SkBitmap::extractSubset())
|
|
|
|
// sur->setImmutable();
|
2014-06-23 05:53:14 +08:00
|
|
|
}
|
2017-08-12 02:53:04 +08:00
|
|
|
else {
|
|
|
|
ASSERT(!sur);
|
|
|
|
}
|
2010-03-25 08:35:44 +08:00
|
|
|
|
2014-06-23 05:53:14 +08:00
|
|
|
return sur;
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
|
2023-04-13 04:28:12 +08:00
|
|
|
os::SurfaceRef SkinTheme::sliceSheet(os::SurfaceRef sur, const gfx::Rect& bounds)
|
|
|
|
{
|
|
|
|
return app::skin::sliceSheet(m_sheet, sur, bounds);
|
|
|
|
}
|
|
|
|
|
|
|
|
os::SurfaceRef SkinTheme::sliceUnscaledSheet(os::SurfaceRef sur, const gfx::Rect& bounds)
|
|
|
|
{
|
|
|
|
return app::skin::sliceSheet(m_unscaledSheet, sur, bounds);
|
|
|
|
}
|
|
|
|
|
2024-11-30 04:57:41 +08:00
|
|
|
text::FontRef SkinTheme::getWidgetFont(const Widget* widget) const
|
2015-05-06 06:14:33 +08:00
|
|
|
{
|
2019-08-02 06:14:46 +08:00
|
|
|
auto skinPropery = std::static_pointer_cast<SkinProperty>(
|
|
|
|
widget->getProperty(SkinProperty::Name));
|
2015-05-06 06:14:33 +08:00
|
|
|
if (skinPropery && skinPropery->hasMiniFont())
|
|
|
|
return getMiniFont();
|
|
|
|
else
|
|
|
|
return getDefaultFont();
|
|
|
|
}
|
|
|
|
|
2017-04-07 05:41:18 +08:00
|
|
|
Cursor* SkinTheme::getStandardCursor(CursorType type)
|
2009-11-22 04:02:31 +08:00
|
|
|
{
|
2017-04-07 05:41:18 +08:00
|
|
|
if (type >= kFirstCursorType && type <= kLastCursorType)
|
|
|
|
return m_standardCursors[type];
|
|
|
|
else
|
|
|
|
return nullptr;
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
|
2013-01-14 09:39:44 +08:00
|
|
|
void SkinTheme::initWidget(Widget* widget)
|
2009-11-22 04:02:31 +08:00
|
|
|
{
|
2015-06-24 06:20:49 +08:00
|
|
|
#define BORDER(n) widget->setBorder(gfx::Border(n))
|
|
|
|
|
|
|
|
#define BORDER4(L, T, R, B) widget->setBorder(gfx::Border((L), (T), (R), (B)))
|
2010-03-08 00:21:24 +08:00
|
|
|
|
2017-04-15 19:13:20 +08:00
|
|
|
const int scale = guiscale();
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2015-06-24 01:37:22 +08:00
|
|
|
switch (widget->type()) {
|
2013-04-04 09:07:24 +08:00
|
|
|
case kBoxWidget:
|
2017-03-14 09:31:57 +08:00
|
|
|
widget->setStyle(styles.box());
|
2009-11-22 04:02:31 +08:00
|
|
|
BORDER(0);
|
2015-06-24 06:20:49 +08:00
|
|
|
widget->setChildSpacing(4 * scale);
|
2009-11-22 04:02:31 +08:00
|
|
|
break;
|
|
|
|
|
2017-03-14 09:31:57 +08:00
|
|
|
case kButtonWidget: widget->setStyle(styles.button()); break;
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2017-03-18 02:43:42 +08:00
|
|
|
case kCheckWidget: widget->setStyle(styles.checkBox()); break;
|
2011-03-07 03:15:05 +08:00
|
|
|
|
2017-03-18 02:43:42 +08:00
|
|
|
case kRadioWidget: widget->setStyle(styles.radioButton()); break;
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2013-04-04 09:07:24 +08:00
|
|
|
case kEntryWidget:
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
BORDER4(parts.sunkenNormal()->bitmapW()->width(),
|
|
|
|
parts.sunkenNormal()->bitmapN()->height(),
|
|
|
|
parts.sunkenNormal()->bitmapE()->width(),
|
|
|
|
parts.sunkenNormal()->bitmapS()->height());
|
2015-12-01 02:08:18 +08:00
|
|
|
widget->setChildSpacing(3 * scale);
|
2009-11-22 04:02:31 +08:00
|
|
|
break;
|
|
|
|
|
2013-04-04 09:07:24 +08:00
|
|
|
case kGridWidget:
|
2017-03-14 09:31:57 +08:00
|
|
|
widget->setStyle(styles.grid());
|
2009-11-22 04:02:31 +08:00
|
|
|
BORDER(0);
|
2015-06-24 06:20:49 +08:00
|
|
|
widget->setChildSpacing(4 * scale);
|
2022-12-13 00:54:16 +08:00
|
|
|
static_cast<ui::Grid*>(widget)->setGap(styles.grid()->gap());
|
2009-11-22 04:02:31 +08:00
|
|
|
break;
|
|
|
|
|
2017-03-14 09:31:57 +08:00
|
|
|
case kLabelWidget: widget->setStyle(styles.label()); break;
|
2017-02-15 01:55:45 +08:00
|
|
|
|
2017-03-14 09:31:57 +08:00
|
|
|
case kLinkLabelWidget: widget->setStyle(styles.link()); break;
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2013-04-04 09:07:24 +08:00
|
|
|
case kListBoxWidget:
|
2009-11-22 04:02:31 +08:00
|
|
|
BORDER(0);
|
2015-06-24 06:20:49 +08:00
|
|
|
widget->setChildSpacing(0);
|
2009-11-22 04:02:31 +08:00
|
|
|
break;
|
|
|
|
|
2017-06-12 23:38:53 +08:00
|
|
|
case kListItemWidget: widget->setStyle(styles.listItem()); break;
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2017-02-14 05:34:23 +08:00
|
|
|
case kComboBoxWidget: {
|
|
|
|
ComboBox* combobox = static_cast<ComboBox*>(widget);
|
|
|
|
Button* button = combobox->getButtonWidget();
|
2017-08-15 21:39:06 +08:00
|
|
|
combobox->setChildSpacing(0);
|
2017-03-14 09:31:57 +08:00
|
|
|
button->setStyle(styles.comboboxButton());
|
2009-11-22 04:02:31 +08:00
|
|
|
break;
|
2017-02-14 05:34:23 +08:00
|
|
|
}
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2017-03-14 09:31:57 +08:00
|
|
|
case kMenuWidget: widget->setStyle(styles.menu()); break;
|
2017-02-18 01:18:47 +08:00
|
|
|
|
2017-03-14 09:31:57 +08:00
|
|
|
case kMenuBarWidget: widget->setStyle(styles.menubar()); break;
|
2017-02-18 01:18:47 +08:00
|
|
|
|
2017-03-14 09:31:57 +08:00
|
|
|
case kMenuBoxWidget: widget->setStyle(styles.menubox()); break;
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2013-04-04 09:07:24 +08:00
|
|
|
case kMenuItemWidget:
|
2010-03-09 10:42:31 +08:00
|
|
|
BORDER(2 * scale);
|
2015-06-24 06:20:49 +08:00
|
|
|
widget->setChildSpacing(18 * scale);
|
2009-11-22 04:02:31 +08:00
|
|
|
break;
|
|
|
|
|
2013-04-04 09:07:24 +08:00
|
|
|
case kSplitterWidget:
|
2015-06-24 06:20:49 +08:00
|
|
|
widget->setChildSpacing(3 * scale);
|
2017-03-14 09:31:57 +08:00
|
|
|
widget->setStyle(styles.splitter());
|
2009-11-22 04:02:31 +08:00
|
|
|
break;
|
|
|
|
|
2013-04-04 09:07:24 +08:00
|
|
|
case kSeparatorWidget:
|
2015-05-21 23:28:21 +08:00
|
|
|
// Horizontal bar
|
2017-02-18 05:00:10 +08:00
|
|
|
if (widget->align() & HORIZONTAL) {
|
2017-08-15 21:39:06 +08:00
|
|
|
if (dynamic_cast<MenuSeparator*>(widget)) {
|
2017-03-14 09:31:57 +08:00
|
|
|
widget->setStyle(styles.menuSeparator());
|
2017-08-15 21:39:06 +08:00
|
|
|
BORDER(2 * scale);
|
|
|
|
}
|
2017-02-18 05:00:10 +08:00
|
|
|
else
|
2017-03-14 09:31:57 +08:00
|
|
|
widget->setStyle(styles.horizontalSeparator());
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
2015-05-21 23:28:21 +08:00
|
|
|
// Vertical bar
|
2009-11-22 04:02:31 +08:00
|
|
|
else {
|
2017-03-14 09:31:57 +08:00
|
|
|
widget->setStyle(styles.verticalSeparator());
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-05-02 10:31:10 +08:00
|
|
|
case kSliderWidget: widget->setStyle(styles.slider()); break;
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2013-04-04 09:07:24 +08:00
|
|
|
case kTextBoxWidget:
|
2015-06-24 06:20:49 +08:00
|
|
|
widget->setChildSpacing(0);
|
2022-07-30 05:42:20 +08:00
|
|
|
widget->setStyle(styles.textboxText());
|
2009-11-22 04:02:31 +08:00
|
|
|
break;
|
|
|
|
|
2024-12-18 20:19:30 +08:00
|
|
|
case kTextEditWidget: widget->setStyle(styles.textedit()); break;
|
|
|
|
|
2013-04-04 09:07:24 +08:00
|
|
|
case kViewWidget:
|
2015-06-24 06:20:49 +08:00
|
|
|
widget->setChildSpacing(0);
|
2015-02-16 02:29:16 +08:00
|
|
|
widget->setBgColor(colors.windowFace());
|
2017-03-14 09:31:57 +08:00
|
|
|
widget->setStyle(styles.view());
|
2009-11-22 04:02:31 +08:00
|
|
|
break;
|
|
|
|
|
2013-04-04 09:07:24 +08:00
|
|
|
case kViewScrollbarWidget:
|
2017-03-14 09:31:57 +08:00
|
|
|
widget->setStyle(styles.scrollbar());
|
|
|
|
static_cast<ScrollBar*>(widget)->setThumbStyle(styles.scrollbarThumb());
|
2009-11-22 04:02:31 +08:00
|
|
|
break;
|
|
|
|
|
2013-04-04 09:07:24 +08:00
|
|
|
case kViewViewportWidget:
|
2009-11-22 04:02:31 +08:00
|
|
|
BORDER(0);
|
2015-06-24 06:20:49 +08:00
|
|
|
widget->setChildSpacing(0);
|
2009-11-22 04:02:31 +08:00
|
|
|
break;
|
|
|
|
|
2017-03-14 09:31:57 +08:00
|
|
|
case kManagerWidget: widget->setStyle(styles.desktop()); break;
|
2017-02-18 01:37:58 +08:00
|
|
|
|
2013-04-04 09:07:24 +08:00
|
|
|
case kWindowWidget:
|
2017-02-18 01:18:47 +08:00
|
|
|
if (TipWindow* window = dynamic_cast<TipWindow*>(widget)) {
|
2017-03-14 09:31:57 +08:00
|
|
|
window->setStyle(styles.tooltipWindow());
|
|
|
|
window->setArrowStyle(styles.tooltipWindowArrow());
|
2022-02-19 06:01:46 +08:00
|
|
|
window->textBox()->setStyle(styles.tooltipText());
|
2017-02-18 01:18:47 +08:00
|
|
|
}
|
|
|
|
else if (dynamic_cast<TransparentPopupWindow*>(widget)) {
|
2017-03-14 09:31:57 +08:00
|
|
|
widget->setStyle(styles.transparentPopupWindow());
|
2017-02-18 01:18:47 +08:00
|
|
|
}
|
|
|
|
else if (dynamic_cast<PopupWindow*>(widget)) {
|
2017-03-14 09:31:57 +08:00
|
|
|
widget->setStyle(styles.popupWindow());
|
2017-02-18 01:18:47 +08:00
|
|
|
}
|
|
|
|
else if (static_cast<Window*>(widget)->isDesktop()) {
|
2017-03-14 09:31:57 +08:00
|
|
|
widget->setStyle(styles.desktop());
|
2017-02-18 01:18:47 +08:00
|
|
|
}
|
|
|
|
else {
|
2012-01-06 06:45:03 +08:00
|
|
|
if (widget->hasText()) {
|
2017-03-14 09:31:57 +08:00
|
|
|
widget->setStyle(styles.windowWithTitle());
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
else {
|
2017-03-14 09:31:57 +08:00
|
|
|
widget->setStyle(styles.windowWithoutTitle());
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
2017-02-18 01:18:47 +08:00
|
|
|
break;
|
2015-12-11 03:46:01 +08:00
|
|
|
|
2022-02-19 06:01:46 +08:00
|
|
|
case kWindowTitleLabelWidget: widget->setStyle(styles.windowTitleLabel()); break;
|
2015-12-11 03:46:01 +08:00
|
|
|
|
2022-02-19 06:01:46 +08:00
|
|
|
case kWindowCloseButtonWidget: widget->setStyle(styles.windowCloseButton()); break;
|
2009-11-22 04:02:31 +08:00
|
|
|
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-14 09:39:44 +08:00
|
|
|
void SkinTheme::getWindowMask(Widget* widget, Region& region)
|
2009-11-22 04:02:31 +08:00
|
|
|
{
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
region = widget->bounds();
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
|
2015-08-13 03:48:40 +08:00
|
|
|
int SkinTheme::getScrollbarSize()
|
|
|
|
{
|
2015-08-13 03:56:19 +08:00
|
|
|
return dimensions.scrollbarSize();
|
2015-08-13 03:48:40 +08:00
|
|
|
}
|
|
|
|
|
2025-10-07 22:08:41 +08:00
|
|
|
gfx::Size SkinTheme::getCaretSize(Widget* widget)
|
2017-02-08 06:05:47 +08:00
|
|
|
{
|
2025-10-07 22:08:41 +08:00
|
|
|
int caretHeight;
|
|
|
|
if (widget->type() == kTextEditWidget) {
|
|
|
|
// We cannot use the height of the widget text, because it
|
|
|
|
// includes the line height of every single line in the widget.
|
|
|
|
caretHeight = widget->font()->lineHeight();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
caretHeight = widget->textHeight();
|
|
|
|
}
|
|
|
|
|
2024-02-21 02:40:17 +08:00
|
|
|
if (widget->font()->type() == text::FontType::FreeType)
|
2025-10-07 22:08:41 +08:00
|
|
|
return gfx::Size(2 * guiscale(), caretHeight);
|
2017-02-08 06:05:47 +08:00
|
|
|
else
|
2025-10-07 22:08:41 +08:00
|
|
|
return gfx::Size(2 * guiscale(), caretHeight + 2 * guiscale());
|
2017-02-08 06:05:47 +08:00
|
|
|
}
|
|
|
|
|
2011-02-12 20:32:57 +08:00
|
|
|
void SkinTheme::paintEntry(PaintEvent& ev)
|
2009-11-22 04:02:31 +08:00
|
|
|
{
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
Graphics* g = ev.graphics();
|
2011-02-12 20:32:57 +08:00
|
|
|
Entry* widget = static_cast<Entry*>(ev.getSource());
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
gfx::Rect bounds = widget->clientBounds();
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2013-03-31 00:27:38 +08:00
|
|
|
// Outside borders
|
2025-02-26 20:13:38 +08:00
|
|
|
const gfx::Color borders = BGCOLOR;
|
|
|
|
if (!is_transparent(borders))
|
|
|
|
g->fillRect(borders, bounds);
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2012-01-10 08:18:32 +08:00
|
|
|
bool isMiniLook = false;
|
2019-08-02 06:14:46 +08:00
|
|
|
auto skinPropery = std::static_pointer_cast<SkinProperty>(
|
|
|
|
widget->getProperty(SkinProperty::Name));
|
2015-04-03 07:42:43 +08:00
|
|
|
if (skinPropery)
|
2012-01-10 08:18:32 +08:00
|
|
|
isMiniLook = (skinPropery->getLook() == MiniLook);
|
|
|
|
|
2025-02-26 20:13:38 +08:00
|
|
|
// Inside background
|
|
|
|
gfx::Color bg;
|
|
|
|
if (widget->isReadOnly())
|
|
|
|
bg = colors.windowFace();
|
|
|
|
else
|
|
|
|
bg = gfx::ColorNone;
|
|
|
|
|
2024-06-12 09:31:13 +08:00
|
|
|
drawRect(g,
|
|
|
|
bounds,
|
2014-04-20 02:18:16 +08:00
|
|
|
(widget->hasFocus() ?
|
2015-08-05 06:38:52 +08:00
|
|
|
(isMiniLook ? parts.sunkenMiniFocused().get() : parts.sunkenFocused().get()) :
|
2024-06-12 09:31:13 +08:00
|
|
|
(isMiniLook ? parts.sunkenMiniNormal().get() : parts.sunkenNormal().get())),
|
2025-02-26 20:13:38 +08:00
|
|
|
is_transparent(bg)); // Paint center if there is no special background color
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2017-02-07 04:58:55 +08:00
|
|
|
drawEntryText(g, widget);
|
|
|
|
}
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2017-02-07 04:58:55 +08:00
|
|
|
namespace {
|
2015-04-06 22:53:14 +08:00
|
|
|
|
2024-02-21 02:40:17 +08:00
|
|
|
class DrawEntryTextDelegate : public text::DrawTextDelegate {
|
2017-02-07 04:58:55 +08:00
|
|
|
public:
|
|
|
|
DrawEntryTextDelegate(Entry* widget, Graphics* graphics, const gfx::Point& pos, const int h)
|
|
|
|
: m_widget(widget)
|
|
|
|
, m_graphics(graphics)
|
|
|
|
, m_caretDrawn(false)
|
|
|
|
// m_lastX is an absolute position on screen
|
|
|
|
, m_lastX(pos.x + m_widget->bounds().x)
|
|
|
|
, m_y(pos.y)
|
|
|
|
, m_h(h)
|
|
|
|
{
|
2020-05-16 05:24:35 +08:00
|
|
|
m_widget->getEntryThemeInfo(&m_index, &m_caret, &m_state, &m_range);
|
2025-07-02 20:31:26 +08:00
|
|
|
m_suffixIndex = m_widget->text().size();
|
2017-02-07 04:58:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int index() const { return m_index; }
|
|
|
|
bool caretDrawn() const { return m_caretDrawn; }
|
|
|
|
const gfx::Rect& textBounds() const { return m_textBounds; }
|
|
|
|
|
2017-02-22 05:05:23 +08:00
|
|
|
void preProcessChar(const int index,
|
2024-03-08 06:23:38 +08:00
|
|
|
const base::codepoint_t codepoint,
|
2017-02-07 04:58:55 +08:00
|
|
|
gfx::Color& fg,
|
2020-03-02 10:42:08 +08:00
|
|
|
gfx::Color& bg,
|
|
|
|
const gfx::Rect& charBounds) override
|
|
|
|
{
|
2022-02-19 06:01:46 +08:00
|
|
|
auto theme = SkinTheme::get(m_widget);
|
|
|
|
|
2013-01-07 01:45:43 +08:00
|
|
|
// Normal text
|
2022-02-19 06:01:46 +08:00
|
|
|
auto& colors = theme->colors;
|
2013-01-07 01:45:43 +08:00
|
|
|
bg = ColorNone;
|
2017-02-07 04:58:55 +08:00
|
|
|
fg = colors.text();
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2025-07-02 20:31:26 +08:00
|
|
|
// Suffix text
|
|
|
|
if (m_index >= m_suffixIndex) {
|
|
|
|
fg = colors.entrySuffix();
|
|
|
|
}
|
|
|
|
|
2013-01-07 01:45:43 +08:00
|
|
|
// Selected
|
2020-05-16 05:24:35 +08:00
|
|
|
if ((m_index >= m_range.from) && (m_index < m_range.to)) {
|
2017-02-07 04:58:55 +08:00
|
|
|
if (m_widget->hasFocus())
|
2015-02-16 02:29:16 +08:00
|
|
|
bg = colors.selected();
|
2009-11-22 04:02:31 +08:00
|
|
|
else
|
2015-02-16 02:29:16 +08:00
|
|
|
bg = colors.disabled();
|
2018-01-30 01:09:22 +08:00
|
|
|
fg = colors.selectedText();
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
|
2013-01-07 01:45:43 +08:00
|
|
|
// Disabled
|
2017-02-07 04:58:55 +08:00
|
|
|
if (!m_widget->isEnabled()) {
|
2013-03-30 03:27:08 +08:00
|
|
|
bg = ColorNone;
|
2015-02-16 02:29:16 +08:00
|
|
|
fg = colors.disabled();
|
2013-03-30 03:27:08 +08:00
|
|
|
}
|
|
|
|
|
2017-02-07 04:58:55 +08:00
|
|
|
m_bg = bg;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool preDrawChar(const gfx::Rect& charBounds) override
|
|
|
|
{
|
2017-02-08 06:05:47 +08:00
|
|
|
m_textBounds |= charBounds;
|
|
|
|
m_charStartX = charBounds.x;
|
|
|
|
|
2017-02-07 04:58:55 +08:00
|
|
|
if (charBounds.x2() - m_widget->bounds().x < m_widget->clientBounds().x2()) {
|
|
|
|
if (m_bg != ColorNone) {
|
|
|
|
// Fill background e.g. needed for selected/highlighted
|
|
|
|
// regions with TTF fonts where the char is smaller than the
|
|
|
|
// text bounds [m_y,m_y+m_h)
|
|
|
|
gfx::Rect fillThisRect(m_lastX - m_widget->bounds().x, m_y, charBounds.x2() - m_lastX, m_h);
|
|
|
|
if (charBounds != fillThisRect)
|
|
|
|
m_graphics->fillRect(m_bg, fillThisRect);
|
|
|
|
}
|
|
|
|
m_lastX = charBounds.x2();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2017-02-07 04:58:55 +08:00
|
|
|
void postDrawChar(const gfx::Rect& charBounds) override
|
|
|
|
{
|
2013-01-07 01:45:43 +08:00
|
|
|
// Caret
|
2017-02-07 04:58:55 +08:00
|
|
|
if (m_state && m_index == m_caret && m_widget->hasFocus() && m_widget->isEnabled()) {
|
2022-02-19 06:01:46 +08:00
|
|
|
auto theme = SkinTheme::get(m_widget);
|
2017-02-08 06:05:47 +08:00
|
|
|
theme->drawEntryCaret(m_graphics, m_widget, m_charStartX - m_widget->bounds().x, m_y);
|
2017-02-07 04:58:55 +08:00
|
|
|
m_caretDrawn = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
++m_index;
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
|
2017-02-07 04:58:55 +08:00
|
|
|
private:
|
|
|
|
Entry* m_widget;
|
|
|
|
Graphics* m_graphics;
|
|
|
|
int m_index;
|
|
|
|
int m_caret;
|
|
|
|
int m_state;
|
2020-05-16 05:24:35 +08:00
|
|
|
Entry::Range m_range;
|
2017-02-07 04:58:55 +08:00
|
|
|
gfx::Rect m_textBounds;
|
|
|
|
bool m_caretDrawn;
|
|
|
|
gfx::Color m_bg;
|
2017-02-08 06:05:47 +08:00
|
|
|
int m_lastX; // Last position used to fill the background
|
2017-02-07 04:58:55 +08:00
|
|
|
int m_y, m_h;
|
2017-02-08 06:05:47 +08:00
|
|
|
int m_charStartX;
|
2025-07-02 20:31:26 +08:00
|
|
|
int m_suffixIndex;
|
2017-02-07 04:58:55 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
|
|
void SkinTheme::drawEntryText(ui::Graphics* g, ui::Entry* widget)
|
|
|
|
{
|
|
|
|
// Draw the text
|
|
|
|
gfx::Rect bounds = widget->getEntryTextBounds();
|
|
|
|
|
|
|
|
DrawEntryTextDelegate delegate(widget, g, bounds.origin(), widget->textHeight());
|
|
|
|
int scroll = delegate.index();
|
|
|
|
|
2025-08-26 15:21:44 +08:00
|
|
|
const std::string& fullText = widget->text() + widget->getSuffix();
|
2025-07-02 20:31:26 +08:00
|
|
|
|
2025-08-26 15:21:44 +08:00
|
|
|
// Full text to paint: widget text + suffix or placeholder
|
|
|
|
const std::string& paintText = fullText.empty() ? widget->placeholder() : fullText;
|
|
|
|
|
|
|
|
if (!paintText.empty()) {
|
|
|
|
base::utf8_decode dec(paintText);
|
2024-10-29 02:28:01 +08:00
|
|
|
auto pos = dec.pos();
|
|
|
|
for (int i = 0; i < scroll && dec.next(); ++i)
|
|
|
|
pos = dec.pos();
|
|
|
|
|
2024-10-29 02:32:41 +08:00
|
|
|
IntersectClip clip(g, bounds);
|
|
|
|
if (clip) {
|
2025-07-02 20:31:26 +08:00
|
|
|
int baselineAdjustment = widget->textBaseline();
|
|
|
|
if (auto blob = widget->textBlob()) {
|
|
|
|
baselineAdjustment -= blob->baseline();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
text::FontMetrics metrics;
|
|
|
|
widget->font()->metrics(&metrics);
|
|
|
|
baselineAdjustment += metrics.ascent;
|
|
|
|
}
|
2017-02-07 04:58:55 +08:00
|
|
|
|
2025-08-26 15:21:44 +08:00
|
|
|
g->drawTextWithDelegate(std::string(pos, paintText.end()), // TODO use a string_view()
|
|
|
|
fullText.empty() ? colors.disabled() : colors.text(),
|
2025-07-02 20:31:26 +08:00
|
|
|
ColorNone,
|
|
|
|
gfx::Point(bounds.x, baselineAdjustment),
|
|
|
|
&delegate);
|
2015-11-25 06:54:10 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-07 04:58:55 +08:00
|
|
|
// Draw caret at the end of the text
|
|
|
|
if (!delegate.caretDrawn()) {
|
2025-07-08 09:21:58 +08:00
|
|
|
bounds.x += delegate.textBounds().w;
|
|
|
|
|
2017-02-08 06:05:47 +08:00
|
|
|
gfx::Rect charBounds(bounds.x + widget->bounds().x,
|
|
|
|
bounds.y + widget->bounds().y,
|
|
|
|
0,
|
|
|
|
widget->textHeight());
|
|
|
|
delegate.preDrawChar(charBounds);
|
|
|
|
delegate.postDrawChar(charBounds);
|
2013-01-07 01:45:43 +08:00
|
|
|
}
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
|
2013-05-21 07:40:18 +08:00
|
|
|
void SkinTheme::paintListBox(PaintEvent& ev)
|
2009-11-22 04:02:31 +08:00
|
|
|
{
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
Graphics* g = ev.graphics();
|
2013-05-21 07:40:18 +08:00
|
|
|
|
2015-02-16 02:29:16 +08:00
|
|
|
g->fillRect(colors.background(), g->getClipBounds());
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
|
2013-05-21 07:40:18 +08:00
|
|
|
void SkinTheme::paintMenu(PaintEvent& ev)
|
2009-11-22 04:02:31 +08:00
|
|
|
{
|
2013-05-21 07:40:18 +08:00
|
|
|
Widget* widget = static_cast<Widget*>(ev.getSource());
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
Graphics* g = ev.graphics();
|
2013-05-21 07:40:18 +08:00
|
|
|
|
|
|
|
g->fillRect(BGCOLOR, g->getClipBounds());
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
|
2013-05-21 07:40:18 +08:00
|
|
|
void SkinTheme::paintMenuItem(ui::PaintEvent& ev)
|
2009-11-22 04:02:31 +08:00
|
|
|
{
|
2014-11-26 09:33:45 +08:00
|
|
|
int scale = guiscale();
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
Graphics* g = ev.graphics();
|
2014-11-07 05:41:18 +08:00
|
|
|
MenuItem* widget = static_cast<MenuItem*>(ev.getSource());
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
gfx::Rect bounds = widget->clientBounds();
|
2014-06-29 03:10:39 +08:00
|
|
|
gfx::Color fg, bg;
|
2013-01-07 01:45:43 +08:00
|
|
|
int c, bar;
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2013-01-07 01:45:43 +08:00
|
|
|
// TODO ASSERT?
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
if (!widget->parent()->parent())
|
2009-11-22 04:02:31 +08:00
|
|
|
return;
|
|
|
|
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
bar = (widget->parent()->parent()->type() == kMenuBarWidget);
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2013-01-07 01:45:43 +08:00
|
|
|
// Colors
|
2010-07-04 02:26:27 +08:00
|
|
|
if (!widget->isEnabled()) {
|
2013-01-07 01:45:43 +08:00
|
|
|
fg = ColorNone;
|
2015-02-16 02:29:16 +08:00
|
|
|
bg = colors.menuitemNormalFace();
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
else {
|
2011-04-23 10:00:35 +08:00
|
|
|
if (widget->isHighlighted()) {
|
2015-02-16 02:29:16 +08:00
|
|
|
fg = colors.menuitemHighlightText();
|
|
|
|
bg = colors.menuitemHighlightFace();
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
2014-04-25 19:59:00 +08:00
|
|
|
else if (widget->hasMouse()) {
|
2015-02-16 02:29:16 +08:00
|
|
|
fg = colors.menuitemHotText();
|
|
|
|
bg = colors.menuitemHotFace();
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
else {
|
2015-02-16 02:29:16 +08:00
|
|
|
fg = colors.menuitemNormalText();
|
|
|
|
bg = colors.menuitemNormalFace();
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-20 02:18:16 +08:00
|
|
|
// Background
|
|
|
|
g->fillRect(bg, bounds);
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2014-04-20 02:18:16 +08:00
|
|
|
// Draw an indicator for selected items
|
2010-07-04 02:03:26 +08:00
|
|
|
if (widget->isSelected()) {
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
os::Surface* icon = (widget->isEnabled() ? parts.checkSelected()->bitmap(0) :
|
|
|
|
parts.checkDisabled()->bitmap(0));
|
2014-06-23 05:53:14 +08:00
|
|
|
|
|
|
|
int x = bounds.x + 4 * scale - icon->width() / 2;
|
|
|
|
int y = bounds.y + bounds.h / 2 - icon->height() / 2;
|
|
|
|
g->drawRgbaSurface(icon, x, y);
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
|
2014-04-20 02:18:16 +08:00
|
|
|
// Text
|
2009-11-22 04:02:31 +08:00
|
|
|
if (bar)
|
2015-06-24 01:00:00 +08:00
|
|
|
widget->setAlign(CENTER | MIDDLE);
|
2009-11-22 04:02:31 +08:00
|
|
|
else
|
2015-06-24 01:00:00 +08:00
|
|
|
widget->setAlign(LEFT | MIDDLE);
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2014-04-20 02:18:16 +08:00
|
|
|
Rect pos = bounds;
|
2009-11-22 04:02:31 +08:00
|
|
|
if (!bar)
|
2015-06-24 06:20:49 +08:00
|
|
|
pos.offset(widget->childSpacing() / 2, 0);
|
2020-05-02 10:31:10 +08:00
|
|
|
drawText(g, nullptr, fg, ColorNone, widget, pos, widget->align(), widget->mnemonic());
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2013-05-21 07:40:18 +08:00
|
|
|
// For menu-box
|
2009-11-22 04:02:31 +08:00
|
|
|
if (!bar) {
|
2013-05-21 07:40:18 +08:00
|
|
|
// Draw the arrown (to indicate which this menu has a sub-menu)
|
2011-04-23 10:00:35 +08:00
|
|
|
if (widget->getSubmenu()) {
|
2013-05-21 07:40:18 +08:00
|
|
|
// Enabled
|
2010-07-04 02:26:27 +08:00
|
|
|
if (widget->isEnabled()) {
|
2012-01-06 06:45:03 +08:00
|
|
|
for (c = 0; c < 3 * scale; c++)
|
2014-04-20 02:18:16 +08:00
|
|
|
g->drawVLine(fg, bounds.x2() - 3 * scale - c, bounds.y + bounds.h / 2 - c, 2 * c + 1);
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
2013-05-21 07:40:18 +08:00
|
|
|
// Disabled
|
2009-11-22 04:02:31 +08:00
|
|
|
else {
|
2012-01-06 06:45:03 +08:00
|
|
|
for (c = 0; c < 3 * scale; c++)
|
2015-02-16 02:29:16 +08:00
|
|
|
g->drawVLine(colors.background(),
|
2014-04-20 02:18:16 +08:00
|
|
|
bounds.x2() - 3 * scale - c + 1,
|
|
|
|
bounds.y + bounds.h / 2 - c + 1,
|
|
|
|
2 * c + 1);
|
|
|
|
|
2012-01-06 06:45:03 +08:00
|
|
|
for (c = 0; c < 3 * scale; c++)
|
2015-02-16 02:29:16 +08:00
|
|
|
g->drawVLine(colors.disabled(),
|
2014-04-20 02:18:16 +08:00
|
|
|
bounds.x2() - 3 * scale - c,
|
|
|
|
bounds.y + bounds.h / 2 - c,
|
|
|
|
2 * c + 1);
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
}
|
2012-07-18 08:42:02 +08:00
|
|
|
// Draw the keyboard shortcut
|
2014-11-07 05:41:18 +08:00
|
|
|
else if (AppMenuItem* appMenuItem = dynamic_cast<AppMenuItem*>(widget)) {
|
2025-01-07 07:21:51 +08:00
|
|
|
if (appMenuItem->key() && !appMenuItem->key()->shortcuts().empty()) {
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
int old_align = appMenuItem->align();
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2014-11-07 05:41:18 +08:00
|
|
|
pos = bounds;
|
2015-06-24 06:20:49 +08:00
|
|
|
pos.w -= widget->childSpacing() / 4;
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2025-01-07 07:21:51 +08:00
|
|
|
std::string buf = appMenuItem->key()->shortcuts().front().toString();
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2015-06-24 01:00:00 +08:00
|
|
|
widget->setAlign(RIGHT | MIDDLE);
|
2020-05-02 10:31:10 +08:00
|
|
|
drawText(g, buf.c_str(), fg, ColorNone, widget, pos, widget->align(), 0);
|
2014-11-07 05:41:18 +08:00
|
|
|
widget->setAlign(old_align);
|
|
|
|
}
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-02-05 23:03:22 +08:00
|
|
|
void SkinTheme::paintSlider(PaintEvent& ev)
|
2009-11-22 04:02:31 +08:00
|
|
|
{
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
Graphics* g = ev.graphics();
|
2014-04-20 02:18:16 +08:00
|
|
|
Slider* widget = static_cast<Slider*>(ev.getSource());
|
2020-05-02 10:31:10 +08:00
|
|
|
const Rect bounds = widget->clientBounds();
|
2009-11-22 04:02:31 +08:00
|
|
|
int min, max, value;
|
|
|
|
|
2013-04-04 08:17:12 +08:00
|
|
|
// Outside borders
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
gfx::Color bgcolor = widget->bgColor();
|
2013-04-04 08:17:12 +08:00
|
|
|
if (!is_transparent(bgcolor))
|
2014-04-20 02:18:16 +08:00
|
|
|
g->fillRect(bgcolor, bounds);
|
2013-04-04 08:17:12 +08:00
|
|
|
|
2010-12-05 03:13:21 +08:00
|
|
|
widget->getSliderThemeInfo(&min, &max, &value);
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2020-05-02 10:31:10 +08:00
|
|
|
Rect rc = bounds;
|
|
|
|
rc.shrink(widget->border());
|
2011-02-05 23:03:22 +08:00
|
|
|
int x;
|
2009-11-22 04:02:31 +08:00
|
|
|
if (min != max)
|
2011-02-05 23:03:22 +08:00
|
|
|
x = rc.x + rc.w * (value - min) / (max - min);
|
2009-11-22 04:02:31 +08:00
|
|
|
else
|
2011-02-05 23:03:22 +08:00
|
|
|
x = rc.x;
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2020-05-02 10:31:10 +08:00
|
|
|
rc = bounds;
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2011-01-24 11:03:38 +08:00
|
|
|
// The mini-look is used for sliders with tiny borders.
|
|
|
|
bool isMiniLook = false;
|
|
|
|
|
|
|
|
// The BG painter is used for sliders without a number-indicator and
|
|
|
|
// customized background (e.g. RGB sliders)
|
|
|
|
ISliderBgPainter* bgPainter = NULL;
|
|
|
|
|
2019-08-02 06:14:46 +08:00
|
|
|
const auto skinPropery = std::static_pointer_cast<SkinProperty>(
|
|
|
|
widget->getProperty(SkinProperty::Name));
|
2015-04-03 07:42:43 +08:00
|
|
|
if (skinPropery)
|
2011-03-30 08:07:37 +08:00
|
|
|
isMiniLook = (skinPropery->getLook() == MiniLook);
|
2011-01-24 11:03:38 +08:00
|
|
|
|
2019-08-02 06:14:46 +08:00
|
|
|
const auto skinSliderPropery = std::static_pointer_cast<SkinSliderProperty>(
|
|
|
|
widget->getProperty(SkinSliderProperty::Name));
|
2015-04-03 07:42:43 +08:00
|
|
|
if (skinSliderPropery)
|
2013-12-05 12:19:46 +08:00
|
|
|
bgPainter = skinSliderPropery->getBgPainter();
|
2011-01-24 11:03:38 +08:00
|
|
|
|
|
|
|
// Draw customized background
|
|
|
|
if (bgPainter) {
|
2015-08-05 06:38:52 +08:00
|
|
|
SkinPartPtr nw = parts.miniSliderEmpty();
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
os::Surface* thumb = (widget->hasFocus() ? parts.miniSliderThumbFocused()->bitmap(0) :
|
|
|
|
parts.miniSliderThumb()->bitmap(0));
|
2011-01-24 11:03:38 +08:00
|
|
|
|
|
|
|
// Draw background
|
2011-02-05 23:03:22 +08:00
|
|
|
g->fillRect(BGCOLOR, rc);
|
2011-01-24 11:03:38 +08:00
|
|
|
|
|
|
|
// Draw thumb
|
2016-09-13 04:53:01 +08:00
|
|
|
int thumb_y = rc.y;
|
|
|
|
if (rc.h > thumb->height() * 3)
|
|
|
|
rc.shrink(Border(0, thumb->height(), 0, 0));
|
2011-01-24 11:03:38 +08:00
|
|
|
|
|
|
|
// Draw borders
|
2016-09-13 04:53:01 +08:00
|
|
|
if (rc.h > 4 * guiscale()) {
|
|
|
|
rc.shrink(Border(3, 0, 3, 1) * guiscale());
|
2017-02-11 04:49:37 +08:00
|
|
|
drawRect(g, rc, nw.get());
|
2016-09-13 04:53:01 +08:00
|
|
|
}
|
2010-03-22 08:28:32 +08:00
|
|
|
|
2011-02-05 23:03:22 +08:00
|
|
|
// Draw background (using the customized ISliderBgPainter implementation)
|
2014-11-26 09:33:45 +08:00
|
|
|
rc.shrink(Border(1, 1, 1, 2) * guiscale());
|
2011-02-05 23:03:22 +08:00
|
|
|
if (!rc.isEmpty())
|
|
|
|
bgPainter->paint(widget, g, rc);
|
2016-09-13 04:53:01 +08:00
|
|
|
|
|
|
|
g->drawRgbaSurface(thumb, x - thumb->width() / 2, thumb_y);
|
2010-03-22 08:28:32 +08:00
|
|
|
}
|
|
|
|
else {
|
2011-01-24 11:03:38 +08:00
|
|
|
// Draw borders
|
2015-08-05 06:38:52 +08:00
|
|
|
SkinPartPtr full_part;
|
|
|
|
SkinPartPtr empty_part;
|
2011-01-24 11:03:38 +08:00
|
|
|
|
|
|
|
if (isMiniLook) {
|
2023-07-10 21:54:37 +08:00
|
|
|
full_part = (widget->hasMouse() ? parts.miniSliderFullFocused() : parts.miniSliderFull());
|
|
|
|
empty_part = (widget->hasMouse() ? parts.miniSliderEmptyFocused() : parts.miniSliderEmpty());
|
2011-01-24 11:03:38 +08:00
|
|
|
}
|
|
|
|
else {
|
2023-07-10 21:54:37 +08:00
|
|
|
full_part = (widget->hasFocus() ? parts.sliderFullFocused() : parts.sliderFull());
|
|
|
|
empty_part = (widget->hasFocus() ? parts.sliderEmptyFocused() : parts.sliderEmpty());
|
2011-01-24 11:03:38 +08:00
|
|
|
}
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2011-01-24 11:03:38 +08:00
|
|
|
if (value == min)
|
2017-02-11 04:49:37 +08:00
|
|
|
drawRect(g, rc, empty_part.get());
|
2011-01-24 11:03:38 +08:00
|
|
|
else if (value == max)
|
2017-02-11 04:49:37 +08:00
|
|
|
drawRect(g, rc, full_part.get());
|
2011-01-24 11:03:38 +08:00
|
|
|
else
|
2017-02-11 04:49:37 +08:00
|
|
|
drawRect2(g, rc, x, full_part.get(), empty_part.get());
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2011-01-24 11:03:38 +08:00
|
|
|
// Draw text
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
std::string old_text = widget->text();
|
2015-08-15 06:46:48 +08:00
|
|
|
widget->setTextQuiet(widget->convertValueToText(value));
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2020-05-02 10:31:10 +08:00
|
|
|
gfx::Rect textrc;
|
|
|
|
int textAlign;
|
|
|
|
calcTextInfo(widget, widget->style(), bounds, textrc, textAlign);
|
|
|
|
|
2014-05-05 08:51:52 +08:00
|
|
|
{
|
2020-05-02 10:31:10 +08:00
|
|
|
IntersectClip clip(g, Rect(rc.x, rc.y, x - rc.x + 1, rc.h));
|
2014-05-05 08:51:52 +08:00
|
|
|
if (clip) {
|
2017-02-15 01:16:37 +08:00
|
|
|
drawText(g,
|
|
|
|
nullptr,
|
|
|
|
colors.sliderFullText(),
|
|
|
|
ColorNone,
|
2020-05-02 10:31:10 +08:00
|
|
|
widget,
|
|
|
|
textrc,
|
|
|
|
textAlign,
|
|
|
|
widget->mnemonic());
|
2014-05-05 08:51:52 +08:00
|
|
|
}
|
2011-02-05 23:03:22 +08:00
|
|
|
}
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2014-05-05 08:51:52 +08:00
|
|
|
{
|
|
|
|
IntersectClip clip(g, Rect(x + 1, rc.y, rc.w - (x - rc.x + 1), rc.h));
|
|
|
|
if (clip) {
|
2017-02-15 01:16:37 +08:00
|
|
|
drawText(g,
|
|
|
|
nullptr,
|
|
|
|
colors.sliderEmptyText(),
|
2020-05-02 10:31:10 +08:00
|
|
|
ColorNone,
|
|
|
|
widget,
|
|
|
|
textrc,
|
|
|
|
textAlign,
|
|
|
|
widget->mnemonic());
|
2014-05-05 08:51:52 +08:00
|
|
|
}
|
2011-02-05 23:03:22 +08:00
|
|
|
}
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2010-01-26 08:38:05 +08:00
|
|
|
widget->setTextQuiet(old_text.c_str());
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-21 07:40:18 +08:00
|
|
|
void SkinTheme::paintComboBoxEntry(ui::PaintEvent& ev)
|
2009-11-22 08:26:58 +08:00
|
|
|
{
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
Graphics* g = ev.graphics();
|
2013-05-21 07:40:18 +08:00
|
|
|
Entry* widget = static_cast<Entry*>(ev.getSource());
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
gfx::Rect bounds = widget->clientBounds();
|
2023-03-10 03:41:03 +08:00
|
|
|
ui::Style* style = styles.combobox();
|
2009-11-22 08:26:58 +08:00
|
|
|
|
2023-03-10 03:41:03 +08:00
|
|
|
paintWidget(g, widget, style, bounds);
|
2017-02-07 04:58:55 +08:00
|
|
|
drawEntryText(g, widget);
|
2009-11-22 08:26:58 +08:00
|
|
|
}
|
|
|
|
|
2013-05-21 07:40:18 +08:00
|
|
|
void SkinTheme::paintTextBox(ui::PaintEvent& ev)
|
2009-11-22 04:02:31 +08:00
|
|
|
{
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
Graphics* g = ev.graphics();
|
2013-05-21 07:40:18 +08:00
|
|
|
Widget* widget = static_cast<Widget*>(ev.getSource());
|
2014-04-20 02:18:16 +08:00
|
|
|
|
2022-07-19 03:00:13 +08:00
|
|
|
Theme::paintTextBoxWithStyle(g, widget);
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
|
2013-01-07 01:45:43 +08:00
|
|
|
void SkinTheme::paintViewViewport(PaintEvent& ev)
|
2009-11-22 04:02:31 +08:00
|
|
|
{
|
2013-01-07 01:45:43 +08:00
|
|
|
Viewport* widget = static_cast<Viewport*>(ev.getSource());
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
Graphics* g = ev.graphics();
|
2014-06-29 03:10:39 +08:00
|
|
|
gfx::Color bg = BGCOLOR;
|
2013-01-07 01:45:43 +08:00
|
|
|
|
2014-04-20 05:51:42 +08:00
|
|
|
if (!is_transparent(bg))
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
g->fillRect(bg, widget->clientBounds());
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
|
2014-06-29 03:10:39 +08:00
|
|
|
gfx::Color SkinTheme::getWidgetBgColor(Widget* widget)
|
2009-11-22 04:02:31 +08:00
|
|
|
{
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
gfx::Color c = widget->bgColor();
|
2012-04-06 06:00:19 +08:00
|
|
|
bool decorative = widget->isDecorative();
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2017-08-15 21:39:06 +08:00
|
|
|
if (!is_transparent(c) || widget->type() == kWindowWidget)
|
|
|
|
return (widget->isTransparent() ? gfx::ColorNone : c);
|
2014-04-20 05:51:42 +08:00
|
|
|
else if (decorative)
|
2015-02-16 02:29:16 +08:00
|
|
|
return colors.selected();
|
2014-04-20 05:51:42 +08:00
|
|
|
else
|
2015-02-16 02:29:16 +08:00
|
|
|
return colors.face();
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
|
2020-05-02 10:31:10 +08:00
|
|
|
void SkinTheme::drawText(Graphics* g,
|
|
|
|
const char* t,
|
|
|
|
const gfx::Color fgColor,
|
|
|
|
const gfx::Color bgColor,
|
|
|
|
const Widget* widget,
|
|
|
|
const Rect& rc,
|
|
|
|
const int textAlign,
|
|
|
|
const int mnemonic)
|
2011-02-05 23:03:22 +08:00
|
|
|
{
|
|
|
|
if (t || widget->hasText()) {
|
2012-06-18 09:02:54 +08:00
|
|
|
Rect textrc;
|
2011-02-05 23:03:22 +08:00
|
|
|
|
2024-11-30 04:57:41 +08:00
|
|
|
g->setFont(widget->font());
|
2011-02-05 23:03:22 +08:00
|
|
|
|
2025-03-14 03:20:11 +08:00
|
|
|
if (!t) {
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
t = widget->text().c_str();
|
2025-03-14 03:20:11 +08:00
|
|
|
textrc.setSize(widget->textSize());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
textrc.setSize(g->measureText(t));
|
|
|
|
}
|
2011-02-05 23:03:22 +08:00
|
|
|
|
|
|
|
// Horizontally text alignment
|
|
|
|
|
2020-05-02 10:31:10 +08:00
|
|
|
if (textAlign & RIGHT)
|
2011-02-05 23:03:22 +08:00
|
|
|
textrc.x = rc.x + rc.w - textrc.w - 1;
|
2020-05-02 10:31:10 +08:00
|
|
|
else if (textAlign & CENTER)
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
textrc.x = rc.center().x - textrc.w / 2;
|
2011-02-05 23:03:22 +08:00
|
|
|
else
|
|
|
|
textrc.x = rc.x;
|
|
|
|
|
|
|
|
// Vertically text alignment
|
|
|
|
|
2020-05-02 10:31:10 +08:00
|
|
|
if (textAlign & BOTTOM)
|
2011-02-05 23:03:22 +08:00
|
|
|
textrc.y = rc.y + rc.h - textrc.h - 1;
|
2020-05-02 10:31:10 +08:00
|
|
|
else if (textAlign & MIDDLE)
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
textrc.y = rc.center().y - textrc.h / 2;
|
2011-02-05 23:03:22 +08:00
|
|
|
else
|
|
|
|
textrc.y = rc.y;
|
|
|
|
|
|
|
|
// Background
|
2020-05-02 10:31:10 +08:00
|
|
|
if (!is_transparent(bgColor)) {
|
2011-02-05 23:03:22 +08:00
|
|
|
if (!widget->isEnabled())
|
2020-05-02 10:31:10 +08:00
|
|
|
g->fillRect(bgColor, Rect(textrc).inflate(guiscale(), guiscale()));
|
2011-02-05 23:03:22 +08:00
|
|
|
else
|
2020-05-02 10:31:10 +08:00
|
|
|
g->fillRect(bgColor, textrc);
|
2011-02-05 23:03:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Text
|
2015-06-10 03:22:47 +08:00
|
|
|
Rect textWrap = textrc
|
|
|
|
.createIntersection(
|
2014-04-20 02:18:16 +08:00
|
|
|
// TODO add ui::Widget::getPadding() property
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
// Rect(widget->clientBounds()).shrink(widget->border()));
|
|
|
|
widget->clientBounds())
|
|
|
|
.inflate(0, 1 * guiscale());
|
2011-02-05 23:03:22 +08:00
|
|
|
|
2014-05-05 08:51:52 +08:00
|
|
|
IntersectClip clip(g, textWrap);
|
|
|
|
if (clip) {
|
2014-04-18 03:24:04 +08:00
|
|
|
if (!widget->isEnabled()) {
|
|
|
|
// Draw white part
|
2017-02-15 01:16:37 +08:00
|
|
|
g->drawUIText(t,
|
2015-02-16 02:29:16 +08:00
|
|
|
colors.background(),
|
2014-06-29 03:10:39 +08:00
|
|
|
gfx::ColorNone,
|
2017-02-15 01:16:37 +08:00
|
|
|
textrc.origin() + Point(guiscale(), guiscale()),
|
|
|
|
mnemonic);
|
2014-04-18 03:24:04 +08:00
|
|
|
}
|
|
|
|
|
2017-02-15 01:16:37 +08:00
|
|
|
g->drawUIText(t,
|
2014-04-18 03:24:04 +08:00
|
|
|
(!widget->isEnabled() ? colors.disabled() :
|
2020-05-02 10:31:10 +08:00
|
|
|
(gfx::geta(fgColor) > 0 ? fgColor : colors.text())),
|
|
|
|
bgColor,
|
|
|
|
textrc.origin(),
|
2017-02-15 01:16:37 +08:00
|
|
|
mnemonic);
|
2014-04-18 03:24:04 +08:00
|
|
|
}
|
2011-02-05 23:03:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-20 02:18:16 +08:00
|
|
|
void SkinTheme::drawEntryCaret(ui::Graphics* g, Entry* widget, int x, int y)
|
2009-11-22 04:02:31 +08:00
|
|
|
{
|
2015-02-16 02:29:16 +08:00
|
|
|
gfx::Color color = colors.text();
|
2017-02-08 06:05:47 +08:00
|
|
|
int textHeight = widget->textHeight();
|
2025-10-07 22:08:41 +08:00
|
|
|
gfx::Size caretSize = getCaretSize(widget);
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2017-02-08 06:05:47 +08:00
|
|
|
for (int u = x; u < x + caretSize.w; ++u)
|
|
|
|
g->drawVLine(color, u, y + textHeight / 2 - caretSize.h / 2, caretSize.h);
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
|
2017-08-15 21:39:06 +08:00
|
|
|
SkinPartPtr SkinTheme::getToolPart(const char* toolId) const
|
|
|
|
{
|
|
|
|
return getPartById(std::string("tool_") + toolId);
|
|
|
|
}
|
|
|
|
|
2018-08-09 23:58:43 +08:00
|
|
|
os::Surface* SkinTheme::getToolIcon(const char* toolId) const
|
- All tools stuff refactored in various files/components.
- Added classes: IToolLoop, Tool, ToolGroup, ToolInk, ToolController, ToolPointShape, ToolIntertwine, ToolBox, etc.
- Added ToolLoopManager.
- Removed old src/modules/tools.cpp.
- Added ISettings and UISettingsImpl, adding the tools settings (onion skinning, grid, tiled mode, etc.).
- Added App::PenSizeBeforeChange, PenSizeAfterChange, CurrentToolChange signals.
- Renamed Context::get_bg/fg_color to getBg/FgColor.
- Refactored Brush class to Pen and added PenType.
- Renamed tiled_t to TiledMode.
- get_config_rect now uses the new Rect class imported from Vaca instead of old jrect.
- Added default_skin.xml to load tool icons.
- Added pen preview in Editor::cursor stuff.
- Added Editor::decorators.
Note: This big patch is from some time ago. I did my best to pre-commit other small changes before this big one.
2010-03-08 03:47:45 +08:00
|
|
|
{
|
2017-08-15 21:39:06 +08:00
|
|
|
SkinPartPtr part = getToolPart(toolId);
|
2017-03-14 09:55:52 +08:00
|
|
|
if (part)
|
|
|
|
return part->bitmap(0);
|
- All tools stuff refactored in various files/components.
- Added classes: IToolLoop, Tool, ToolGroup, ToolInk, ToolController, ToolPointShape, ToolIntertwine, ToolBox, etc.
- Added ToolLoopManager.
- Removed old src/modules/tools.cpp.
- Added ISettings and UISettingsImpl, adding the tools settings (onion skinning, grid, tiled mode, etc.).
- Added App::PenSizeBeforeChange, PenSizeAfterChange, CurrentToolChange signals.
- Renamed Context::get_bg/fg_color to getBg/FgColor.
- Refactored Brush class to Pen and added PenType.
- Renamed tiled_t to TiledMode.
- get_config_rect now uses the new Rect class imported from Vaca instead of old jrect.
- Added default_skin.xml to load tool icons.
- Added pen preview in Editor::cursor stuff.
- Added Editor::decorators.
Note: This big patch is from some time ago. I did my best to pre-commit other small changes before this big one.
2010-03-08 03:47:45 +08:00
|
|
|
else
|
2017-03-14 09:55:52 +08:00
|
|
|
return nullptr;
|
- All tools stuff refactored in various files/components.
- Added classes: IToolLoop, Tool, ToolGroup, ToolInk, ToolController, ToolPointShape, ToolIntertwine, ToolBox, etc.
- Added ToolLoopManager.
- Removed old src/modules/tools.cpp.
- Added ISettings and UISettingsImpl, adding the tools settings (onion skinning, grid, tiled mode, etc.).
- Added App::PenSizeBeforeChange, PenSizeAfterChange, CurrentToolChange signals.
- Renamed Context::get_bg/fg_color to getBg/FgColor.
- Refactored Brush class to Pen and added PenType.
- Renamed tiled_t to TiledMode.
- get_config_rect now uses the new Rect class imported from Vaca instead of old jrect.
- Added default_skin.xml to load tool icons.
- Added pen preview in Editor::cursor stuff.
- Added Editor::decorators.
Note: This big patch is from some time ago. I did my best to pre-commit other small changes before this big one.
2010-03-08 03:47:45 +08:00
|
|
|
}
|
|
|
|
|
2015-08-05 06:38:52 +08:00
|
|
|
void SkinTheme::drawRect(Graphics* g,
|
|
|
|
const Rect& rc,
|
2018-08-09 23:58:43 +08:00
|
|
|
os::Surface* nw,
|
|
|
|
os::Surface* n,
|
|
|
|
os::Surface* ne,
|
|
|
|
os::Surface* e,
|
|
|
|
os::Surface* se,
|
|
|
|
os::Surface* s,
|
|
|
|
os::Surface* sw,
|
|
|
|
os::Surface* w)
|
2011-02-05 23:03:22 +08:00
|
|
|
{
|
|
|
|
int x, y;
|
|
|
|
|
|
|
|
// Top
|
|
|
|
|
2014-06-23 05:53:14 +08:00
|
|
|
g->drawRgbaSurface(nw, rc.x, rc.y);
|
2014-05-05 08:51:52 +08:00
|
|
|
{
|
2014-06-23 05:53:14 +08:00
|
|
|
IntersectClip clip(g, Rect(rc.x + nw->width(), rc.y, rc.w - nw->width() - ne->width(), rc.h));
|
2014-05-05 08:51:52 +08:00
|
|
|
if (clip) {
|
2014-06-23 05:53:14 +08:00
|
|
|
for (x = rc.x + nw->width(); x < rc.x + rc.w - ne->width(); x += n->width()) {
|
|
|
|
g->drawRgbaSurface(n, x, rc.y);
|
2014-05-05 08:51:52 +08:00
|
|
|
}
|
2011-02-05 23:03:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-23 05:53:14 +08:00
|
|
|
g->drawRgbaSurface(ne, rc.x + rc.w - ne->width(), rc.y);
|
2011-02-05 23:03:22 +08:00
|
|
|
|
|
|
|
// Bottom
|
|
|
|
|
2014-06-23 05:53:14 +08:00
|
|
|
g->drawRgbaSurface(sw, rc.x, rc.y + rc.h - sw->height());
|
2014-05-05 08:51:52 +08:00
|
|
|
{
|
2014-06-23 05:53:14 +08:00
|
|
|
IntersectClip clip(g, Rect(rc.x + sw->width(), rc.y, rc.w - sw->width() - se->width(), rc.h));
|
2014-05-05 08:51:52 +08:00
|
|
|
if (clip) {
|
2014-06-23 05:53:14 +08:00
|
|
|
for (x = rc.x + sw->width(); x < rc.x + rc.w - se->width(); x += s->width()) {
|
|
|
|
g->drawRgbaSurface(s, x, rc.y + rc.h - s->height());
|
2014-05-05 08:51:52 +08:00
|
|
|
}
|
2011-02-05 23:03:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-23 05:53:14 +08:00
|
|
|
g->drawRgbaSurface(se, rc.x + rc.w - se->width(), rc.y + rc.h - se->height());
|
2014-05-05 08:51:52 +08:00
|
|
|
{
|
2014-06-23 05:53:14 +08:00
|
|
|
IntersectClip clip(g,
|
|
|
|
Rect(rc.x, rc.y + nw->height(), rc.w, rc.h - nw->height() - sw->height()));
|
2014-05-05 08:51:52 +08:00
|
|
|
if (clip) {
|
|
|
|
// Left
|
2014-06-23 05:53:14 +08:00
|
|
|
for (y = rc.y + nw->height(); y < rc.y + rc.h - sw->height(); y += w->height()) {
|
|
|
|
g->drawRgbaSurface(w, rc.x, y);
|
2014-05-05 08:51:52 +08:00
|
|
|
}
|
2011-02-05 23:03:22 +08:00
|
|
|
|
2014-05-05 08:51:52 +08:00
|
|
|
// Right
|
2014-06-23 05:53:14 +08:00
|
|
|
for (y = rc.y + ne->height(); y < rc.y + rc.h - se->height(); y += e->height()) {
|
|
|
|
g->drawRgbaSurface(e, rc.x + rc.w - e->width(), y);
|
2014-05-05 08:51:52 +08:00
|
|
|
}
|
2011-02-05 23:03:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-11 04:49:37 +08:00
|
|
|
void SkinTheme::drawRect(ui::Graphics* g,
|
|
|
|
const gfx::Rect& rc,
|
|
|
|
SkinPart* skinPart,
|
|
|
|
const bool drawCenter)
|
2009-11-22 04:02:31 +08:00
|
|
|
{
|
2020-07-08 06:06:48 +08:00
|
|
|
Theme::drawSlices(g,
|
|
|
|
m_sheet.get(),
|
|
|
|
rc,
|
2017-02-11 04:49:37 +08:00
|
|
|
skinPart->spriteBounds(),
|
2017-03-27 23:32:39 +08:00
|
|
|
skinPart->slicesBounds(),
|
|
|
|
gfx::ColorNone,
|
|
|
|
drawCenter);
|
2013-12-11 11:34:16 +08:00
|
|
|
}
|
|
|
|
|
2023-04-13 04:28:12 +08:00
|
|
|
void SkinTheme::drawRectUsingUnscaledSheet(ui::Graphics* g,
|
|
|
|
const gfx::Rect& rc,
|
|
|
|
SkinPart* skinPart,
|
|
|
|
const bool drawCenter)
|
|
|
|
{
|
|
|
|
Theme::drawSlices(g,
|
|
|
|
m_unscaledSheet.get(),
|
|
|
|
rc,
|
|
|
|
skinPart->spriteBounds(),
|
|
|
|
skinPart->slicesBounds(),
|
|
|
|
gfx::ColorNone,
|
|
|
|
drawCenter);
|
|
|
|
}
|
|
|
|
|
2015-08-05 06:38:52 +08:00
|
|
|
void SkinTheme::drawRect2(Graphics* g, const Rect& rc, int x_mid, SkinPart* nw1, SkinPart* nw2)
|
2011-02-05 23:03:22 +08:00
|
|
|
{
|
2012-06-18 09:02:54 +08:00
|
|
|
Rect rc2(rc.x, rc.y, x_mid - rc.x + 1, rc.h);
|
2014-05-05 08:51:52 +08:00
|
|
|
{
|
|
|
|
IntersectClip clip(g, rc2);
|
|
|
|
if (clip)
|
2017-02-11 04:49:37 +08:00
|
|
|
drawRect(g, rc, nw1);
|
2014-05-05 08:51:52 +08:00
|
|
|
}
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2011-02-05 23:03:22 +08:00
|
|
|
rc2.x += rc2.w;
|
|
|
|
rc2.w = rc.w - rc2.w;
|
2009-11-22 04:02:31 +08:00
|
|
|
|
2014-05-05 08:51:52 +08:00
|
|
|
IntersectClip clip(g, rc2);
|
|
|
|
if (clip)
|
2017-02-11 04:49:37 +08:00
|
|
|
drawRect(g, rc, nw2);
|
2009-11-22 04:02:31 +08:00
|
|
|
}
|
|
|
|
|
2015-08-05 06:38:52 +08:00
|
|
|
void SkinTheme::drawHline(ui::Graphics* g, const gfx::Rect& rc, SkinPart* part)
|
2013-05-21 07:40:18 +08:00
|
|
|
{
|
|
|
|
int x;
|
|
|
|
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
for (x = rc.x; x < rc.x2() - part->size().w; x += part->size().w) {
|
|
|
|
g->drawRgbaSurface(part->bitmap(0), x, rc.y);
|
2013-05-21 07:40:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (x < rc.x2()) {
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
Rect rc2(x, rc.y, rc.w - (x - rc.x), part->size().h);
|
2014-05-05 08:51:52 +08:00
|
|
|
IntersectClip clip(g, rc2);
|
|
|
|
if (clip)
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
g->drawRgbaSurface(part->bitmap(0), x, rc.y);
|
2013-05-21 07:40:18 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-05 06:38:52 +08:00
|
|
|
void SkinTheme::drawVline(ui::Graphics* g, const gfx::Rect& rc, SkinPart* part)
|
2013-05-21 07:40:18 +08:00
|
|
|
{
|
|
|
|
int y;
|
|
|
|
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
for (y = rc.y; y < rc.y2() - part->size().h; y += part->size().h) {
|
|
|
|
g->drawRgbaSurface(part->bitmap(0), rc.x, y);
|
2013-05-21 07:40:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (y < rc.y2()) {
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
Rect rc2(rc.x, y, part->size().w, rc.h - (y - rc.y));
|
2014-05-05 08:51:52 +08:00
|
|
|
IntersectClip clip(g, rc2);
|
|
|
|
if (clip)
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
g->drawRgbaSurface(part->bitmap(0), rc.x, y);
|
2013-05-21 07:40:18 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-15 09:37:56 +08:00
|
|
|
void SkinTheme::paintProgressBar(ui::Graphics* g, const gfx::Rect& rc0, double progress)
|
2013-12-30 08:12:23 +08:00
|
|
|
{
|
2019-03-26 09:09:22 +08:00
|
|
|
gfx::Color border = colors.text();
|
|
|
|
border = gfx::rgba(gfx::getr(border), gfx::getg(border), gfx::getb(border), 64);
|
|
|
|
g->drawRect(border, rc0);
|
2013-12-30 08:12:23 +08:00
|
|
|
|
|
|
|
gfx::Rect rc = rc0;
|
|
|
|
rc.shrink(1);
|
|
|
|
|
2015-02-15 09:37:56 +08:00
|
|
|
int u = (int)((double)rc.w * progress);
|
2022-06-10 05:28:06 +08:00
|
|
|
u = std::clamp(u, 0, rc.w);
|
2013-12-30 08:12:23 +08:00
|
|
|
|
|
|
|
if (u > 0)
|
2015-02-16 02:29:16 +08:00
|
|
|
g->fillRect(colors.selected(), gfx::Rect(rc.x, rc.y, u, rc.h));
|
2013-12-30 08:12:23 +08:00
|
|
|
|
|
|
|
if (1 + u < rc.w)
|
2015-02-16 02:29:16 +08:00
|
|
|
g->fillRect(colors.background(), gfx::Rect(rc.x + u, rc.y, rc.w - u, rc.h));
|
2013-12-30 08:12:23 +08:00
|
|
|
}
|
|
|
|
|
2017-06-13 22:51:49 +08:00
|
|
|
std::string SkinTheme::findThemePath(const std::string& themeId) const
|
2017-02-10 00:18:44 +08:00
|
|
|
{
|
2017-06-11 02:02:39 +08:00
|
|
|
// First we try to find the theme on an extensions
|
|
|
|
std::string path = App::instance()->extensions().themePath(themeId);
|
|
|
|
if (path.empty()) {
|
|
|
|
// Then we try a theme in the old themes/ folder
|
|
|
|
path = base::join_path(SkinTheme::kThemesFolderName, themeId);
|
|
|
|
path = base::join_path(path, "theme.xml");
|
|
|
|
|
|
|
|
ResourceFinder rf;
|
|
|
|
rf.includeDataDir(path.c_str());
|
|
|
|
if (!rf.findFirst())
|
|
|
|
return std::string();
|
|
|
|
|
|
|
|
path = base::get_file_path(rf.filename());
|
|
|
|
}
|
2017-06-13 22:51:49 +08:00
|
|
|
return base::normalize_path(path);
|
2017-02-10 00:18:44 +08:00
|
|
|
}
|
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
}} // namespace app::skin
|