| 1 | // Aseprite |
| 2 | // Copyright (C) 2019-2022 Igara Studio S.A. |
| 3 | // Copyright (C) 2001-2018 David Capello |
| 4 | // |
| 5 | // This program is distributed under the terms of |
| 6 | // the End-User License Agreement for Aseprite. |
| 7 | |
| 8 | #ifdef HAVE_CONFIG_H |
| 9 | #include "config.h" |
| 10 | #endif |
| 11 | |
| 12 | #include "app/ui/skin/skin_theme.h" |
| 13 | |
| 14 | #include "app/app.h" |
| 15 | #include "app/console.h" |
| 16 | #include "app/extensions.h" |
| 17 | #include "app/font_path.h" |
| 18 | #include "app/modules/gui.h" |
| 19 | #include "app/pref/preferences.h" |
| 20 | #include "app/resource_finder.h" |
| 21 | #include "app/ui/app_menuitem.h" |
| 22 | #include "app/ui/keyboard_shortcuts.h" |
| 23 | #include "app/ui/skin/font_data.h" |
| 24 | #include "app/ui/skin/skin_property.h" |
| 25 | #include "app/ui/skin/skin_slider_property.h" |
| 26 | #include "app/xml_document.h" |
| 27 | #include "app/xml_exception.h" |
| 28 | #include "base/fs.h" |
| 29 | #include "base/log.h" |
| 30 | #include "base/string.h" |
| 31 | #include "base/utf8_decode.h" |
| 32 | #include "gfx/border.h" |
| 33 | #include "gfx/point.h" |
| 34 | #include "gfx/rect.h" |
| 35 | #include "gfx/size.h" |
| 36 | #include "os/draw_text.h" |
| 37 | #include "os/font.h" |
| 38 | #include "os/surface.h" |
| 39 | #include "os/system.h" |
| 40 | #include "ui/intern.h" |
| 41 | #include "ui/ui.h" |
| 42 | |
| 43 | #include "tinyxml.h" |
| 44 | |
| 45 | #include <algorithm> |
| 46 | #include <cstring> |
| 47 | |
| 48 | #define BGCOLOR (getWidgetBgColor(widget)) |
| 49 | |
| 50 | namespace app { |
| 51 | namespace skin { |
| 52 | |
| 53 | using namespace gfx; |
| 54 | using namespace ui; |
| 55 | |
| 56 | // TODO For backward compatibility, in future versions we should remove this (extensions are preferred) |
| 57 | const char* SkinTheme::kThemesFolderName = "themes" ; |
| 58 | |
| 59 | // This class offer backward compatibility with old themes, completing |
| 60 | // or changing styles from the default theme to match the default |
| 61 | // theme of previous versions, so third-party themes can look like |
| 62 | // they are running in the old Aseprite without any modification. |
| 63 | struct app::skin::SkinTheme::BackwardCompatibility { |
| 64 | bool hasSliderStyle = false; |
| 65 | void notifyStyleExistence(const char* styleId) { |
| 66 | if (std::strcmp(styleId, "slider" ) == 0) |
| 67 | hasSliderStyle = true; |
| 68 | } |
| 69 | void createMissingStyles(SkinTheme* theme) { |
| 70 | if (!hasSliderStyle && |
| 71 | theme->styles.slider() && |
| 72 | theme->styles.miniSlider()) { |
| 73 | // Old slider style |
| 74 | ui::Style style(nullptr); |
| 75 | os::Font* font = theme->getDefaultFont(); |
| 76 | const int h = font->height(); |
| 77 | |
| 78 | style.setId(theme->styles.slider()->id()); |
| 79 | style.setFont(AddRef(font)); |
| 80 | |
| 81 | auto part = theme->parts.sliderEmpty(); |
| 82 | style.setBorder( |
| 83 | gfx::Border(part->bitmapW()->width()-1*guiscale(), |
| 84 | part->bitmapN()->height()+h/2, |
| 85 | part->bitmapE()->width()-1*guiscale(), |
| 86 | part->bitmapS()->height()-1*guiscale()+h/2)); |
| 87 | |
| 88 | *theme->styles.slider() = style; |
| 89 | *theme->styles.miniSlider() = style; |
| 90 | } |
| 91 | } |
| 92 | }; |
| 93 | |
| 94 | static const char* g_cursor_names[kCursorTypes] = { |
| 95 | "null" , // kNoCursor |
| 96 | "normal" , // kArrowCursor |
| 97 | "normal_add" , // kArrowPlusCursor |
| 98 | "crosshair" , // kCrosshairCursor |
| 99 | "forbidden" , // kForbiddenCursor |
| 100 | "hand" , // kHandCursor |
| 101 | "scroll" , // kScrollCursor |
| 102 | "move" , // kMoveCursor |
| 103 | "size_ns" , // kSizeNSCursor |
| 104 | "size_we" , // kSizeWECursor |
| 105 | "size_n" , // kSizeNCursor |
| 106 | "size_ne" , // kSizeNECursor |
| 107 | "size_e" , // kSizeECursor |
| 108 | "size_se" , // kSizeSECursor |
| 109 | "size_s" , // kSizeSCursor |
| 110 | "size_sw" , // kSizeSWCursor |
| 111 | "size_w" , // kSizeWCursor |
| 112 | "size_nw" , // kSizeNWCursor |
| 113 | }; |
| 114 | |
| 115 | static FontData* load_font(std::map<std::string, FontData*>& fonts, |
| 116 | const TiXmlElement* xmlFont, |
| 117 | const std::string& xmlFilename) |
| 118 | { |
| 119 | const char* fontRef = xmlFont->Attribute("font" ); |
| 120 | if (fontRef) { |
| 121 | auto it = fonts.find(fontRef); |
| 122 | if (it == fonts.end()) |
| 123 | throw base::Exception("Font named '%s' not found\n" , fontRef); |
| 124 | return it->second; |
| 125 | } |
| 126 | |
| 127 | const char* nameStr = xmlFont->Attribute("name" ); |
| 128 | if (!nameStr) |
| 129 | throw base::Exception("No \"name\" or \"font\" attributes specified on <font>" ); |
| 130 | |
| 131 | std::string name(nameStr); |
| 132 | |
| 133 | // Use cached font data |
| 134 | auto it = fonts.find(name); |
| 135 | if (it != fonts.end()) |
| 136 | return it->second; |
| 137 | |
| 138 | LOG(VERBOSE, "THEME: Loading font '%s'\n" , name.c_str()); |
| 139 | |
| 140 | const char* typeStr = xmlFont->Attribute("type" ); |
| 141 | if (!typeStr) |
| 142 | throw base::Exception("<font> without 'type' attribute in '%s'\n" , |
| 143 | xmlFilename.c_str()); |
| 144 | |
| 145 | std::string type(typeStr); |
| 146 | std::string xmlDir(base::get_file_path(xmlFilename)); |
| 147 | std::unique_ptr<FontData> font(nullptr); |
| 148 | |
| 149 | if (type == "spritesheet" ) { |
| 150 | const char* fileStr = xmlFont->Attribute("file" ); |
| 151 | if (fileStr) { |
| 152 | font.reset(new FontData(os::FontType::SpriteSheet)); |
| 153 | font->setFilename(base::join_path(xmlDir, fileStr)); |
| 154 | } |
| 155 | } |
| 156 | else if (type == "truetype" ) { |
| 157 | const char* platformFileAttrName = |
| 158 | #ifdef _WIN32 |
| 159 | "file_win" |
| 160 | #elif defined __APPLE__ |
| 161 | "file_mac" |
| 162 | #else |
| 163 | "file_linux" |
| 164 | #endif |
| 165 | ; |
| 166 | |
| 167 | const char* platformFileStr = xmlFont->Attribute(platformFileAttrName); |
| 168 | const char* fileStr = xmlFont->Attribute("file" ); |
| 169 | bool antialias = true; |
| 170 | if (xmlFont->Attribute("antialias" )) |
| 171 | antialias = bool_attr(xmlFont, "antialias" , false); |
| 172 | |
| 173 | std::string fontFilename; |
| 174 | if (platformFileStr) |
| 175 | fontFilename = app::find_font(xmlDir, platformFileStr); |
| 176 | if (fileStr && fontFilename.empty()) |
| 177 | fontFilename = app::find_font(xmlDir, fileStr); |
| 178 | |
| 179 | // The filename can be empty if the font was not found, anyway we |
| 180 | // want to keep the font information (e.g. to use the fallback |
| 181 | // information of this font). |
| 182 | font.reset(new FontData(os::FontType::FreeType)); |
| 183 | font->setFilename(fontFilename); |
| 184 | font->setAntialias(antialias); |
| 185 | |
| 186 | if (!fontFilename.empty()) |
| 187 | LOG(VERBOSE, "THEME: Font file '%s' found\n" , fontFilename.c_str()); |
| 188 | } |
| 189 | else { |
| 190 | throw base::Exception("Invalid type=\"%s\" in '%s' for <font name=\"%s\" ...>\n" , |
| 191 | type.c_str(), xmlFilename.c_str(), name.c_str()); |
| 192 | } |
| 193 | |
| 194 | FontData* result = nullptr; |
| 195 | if (font) { |
| 196 | fonts[name] = result = font.get(); |
| 197 | font.release(); |
| 198 | |
| 199 | // Fallback font |
| 200 | const TiXmlElement* xmlFallback = |
| 201 | (const TiXmlElement*)xmlFont->FirstChild("fallback" ); |
| 202 | if (xmlFallback) { |
| 203 | FontData* fallback = load_font(fonts, xmlFallback, xmlFilename); |
| 204 | if (fallback) { |
| 205 | int size = 10; |
| 206 | const char* sizeStr = xmlFont->Attribute("size" ); |
| 207 | if (sizeStr) |
| 208 | size = std::strtol(sizeStr, nullptr, 10); |
| 209 | |
| 210 | result->setFallback(fallback, size); |
| 211 | } |
| 212 | } |
| 213 | } |
| 214 | return result; |
| 215 | } |
| 216 | |
| 217 | // static |
| 218 | SkinTheme* SkinTheme::instance() |
| 219 | { |
| 220 | if (auto mgr = ui::Manager::getDefault()) |
| 221 | return SkinTheme::get(mgr); |
| 222 | else |
| 223 | return nullptr; |
| 224 | } |
| 225 | |
| 226 | // static |
| 227 | SkinTheme* SkinTheme::get(const ui::Widget* widget) |
| 228 | { |
| 229 | ASSERT(widget); |
| 230 | ASSERT(widget->theme()); |
| 231 | ASSERT(dynamic_cast<SkinTheme*>(widget->theme())); |
| 232 | return static_cast<SkinTheme*>(widget->theme()); |
| 233 | } |
| 234 | |
| 235 | SkinTheme::SkinTheme() |
| 236 | : m_sheet(nullptr) |
| 237 | , m_defaultFont(nullptr) |
| 238 | , m_miniFont(nullptr) |
| 239 | , m_preferredScreenScaling(-1) |
| 240 | , m_preferredUIScaling(-1) |
| 241 | { |
| 242 | m_standardCursors.fill(nullptr); |
| 243 | } |
| 244 | |
| 245 | SkinTheme::~SkinTheme() |
| 246 | { |
| 247 | // Delete all cursors. |
| 248 | for (auto& it : m_cursors) |
| 249 | delete it.second; // Delete cursor |
| 250 | |
| 251 | m_sheet.reset(); |
| 252 | m_parts_by_id.clear(); |
| 253 | |
| 254 | // Delete all styles. |
| 255 | for (auto style : m_styles) |
| 256 | delete style.second; |
| 257 | m_styles.clear(); |
| 258 | |
| 259 | // Destroy fonts |
| 260 | for (auto& kv : m_fonts) |
| 261 | delete kv.second; // Delete all FontDatas |
| 262 | m_fonts.clear(); |
| 263 | } |
| 264 | |
| 265 | void SkinTheme::onRegenerateTheme() |
| 266 | { |
| 267 | Preferences& pref = Preferences::instance(); |
| 268 | |
| 269 | // First we load the skin from default theme, which is more proper |
| 270 | // to have every single needed skin part/color/dimension. |
| 271 | loadAll(pref.theme.selected.defaultValue()); |
| 272 | |
| 273 | // Then we load the selected theme to redefine default theme parts. |
| 274 | if (pref.theme.selected.defaultValue() != pref.theme.selected()) { |
| 275 | try { |
| 276 | BackwardCompatibility backward; |
| 277 | loadAll(pref.theme.selected(), &backward); |
| 278 | } |
| 279 | catch (const std::exception& e) { |
| 280 | LOG("THEME: Error loading user-theme: %s\n" , e.what()); |
| 281 | |
| 282 | // Load default theme again |
| 283 | loadAll(pref.theme.selected.defaultValue()); |
| 284 | |
| 285 | if (ui::get_theme()) |
| 286 | Console::showException(e); |
| 287 | |
| 288 | // We can continue, as we've already loaded the default theme |
| 289 | // anyway. Here we restore the setting to its default value. |
| 290 | pref.theme.selected(pref.theme.selected.defaultValue()); |
| 291 | } |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | void SkinTheme::loadFontData() |
| 296 | { |
| 297 | LOG("THEME: Loading fonts\n" ); |
| 298 | |
| 299 | std::string fonstFilename("fonts/fonts.xml" ); |
| 300 | |
| 301 | ResourceFinder rf; |
| 302 | rf.includeDataDir(fonstFilename.c_str()); |
| 303 | if (!rf.findFirst()) |
| 304 | throw base::Exception("File %s not found" , fonstFilename.c_str()); |
| 305 | |
| 306 | XmlDocumentRef doc = open_xml(rf.filename()); |
| 307 | TiXmlHandle handle(doc.get()); |
| 308 | |
| 309 | TiXmlElement* xmlFont = handle |
| 310 | .FirstChild("fonts" ) |
| 311 | .FirstChild("font" ).ToElement(); |
| 312 | while (xmlFont) { |
| 313 | load_font(m_fonts, xmlFont, rf.filename()); |
| 314 | xmlFont = xmlFont->NextSiblingElement(); |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | void SkinTheme::loadAll(const std::string& themeId, |
| 319 | BackwardCompatibility* backward) |
| 320 | { |
| 321 | LOG("THEME: Loading theme %s\n" , themeId.c_str()); |
| 322 | |
| 323 | if (m_fonts.empty()) |
| 324 | loadFontData(); |
| 325 | |
| 326 | m_path = findThemePath(themeId); |
| 327 | if (m_path.empty()) |
| 328 | throw base::Exception("Theme %s not found" , themeId.c_str()); |
| 329 | |
| 330 | loadSheet(); |
| 331 | loadXml(backward); |
| 332 | } |
| 333 | |
| 334 | void SkinTheme::loadSheet() |
| 335 | { |
| 336 | // Load the skin sheet |
| 337 | std::string sheet_filename(base::join_path(m_path, "sheet.png" )); |
| 338 | os::SurfaceRef newSheet; |
| 339 | try { |
| 340 | newSheet = os::instance()->loadRgbaSurface(sheet_filename.c_str()); |
| 341 | } |
| 342 | catch (...) { |
| 343 | // Ignore the error, newSheet is nullptr and we will throw our own |
| 344 | // exception. |
| 345 | } |
| 346 | if (!newSheet) |
| 347 | throw base::Exception("Error loading %s file" , sheet_filename.c_str()); |
| 348 | |
| 349 | // Replace the sprite sheet |
| 350 | if (m_sheet) |
| 351 | m_sheet.reset(); |
| 352 | m_sheet = newSheet; |
| 353 | if (m_sheet) |
| 354 | m_sheet->applyScale(guiscale()); |
| 355 | m_sheet->setImmutable(); |
| 356 | |
| 357 | // Reset sprite sheet and font of all layer styles (to avoid |
| 358 | // dangling pointers to os::Surface or os::Font). |
| 359 | for (auto& it : m_styles) { |
| 360 | for (auto& layer : it.second->layers()) { |
| 361 | layer.setIcon(nullptr); |
| 362 | layer.setSpriteSheet(nullptr); |
| 363 | } |
| 364 | it.second->setFont(nullptr); |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | void SkinTheme::loadXml(BackwardCompatibility* backward) |
| 369 | { |
| 370 | const int scale = guiscale(); |
| 371 | |
| 372 | // Load the skin XML |
| 373 | std::string xml_filename(base::join_path(m_path, "theme.xml" )); |
| 374 | |
| 375 | XmlDocumentRef doc = open_xml(xml_filename); |
| 376 | TiXmlHandle handle(doc.get()); |
| 377 | |
| 378 | // Load Preferred scaling |
| 379 | m_preferredScreenScaling = -1; |
| 380 | m_preferredUIScaling = -1; |
| 381 | { |
| 382 | TiXmlElement* xmlTheme = handle |
| 383 | .FirstChild("theme" ).ToElement(); |
| 384 | if (xmlTheme) { |
| 385 | const char* screenScaling = xmlTheme->Attribute("screenscaling" ); |
| 386 | const char* uiScaling = xmlTheme->Attribute("uiscaling" ); |
| 387 | if (screenScaling) |
| 388 | m_preferredScreenScaling = std::strtol(screenScaling, nullptr, 10); |
| 389 | if (uiScaling) |
| 390 | m_preferredUIScaling = std::strtol(uiScaling, nullptr, 10); |
| 391 | } |
| 392 | } |
| 393 | |
| 394 | // Load fonts |
| 395 | { |
| 396 | TiXmlElement* xmlFont = handle |
| 397 | .FirstChild("theme" ) |
| 398 | .FirstChild("fonts" ) |
| 399 | .FirstChild("font" ).ToElement(); |
| 400 | while (xmlFont) { |
| 401 | const char* idStr = xmlFont->Attribute("id" ); |
| 402 | FontData* fontData = load_font(m_fonts, xmlFont, xml_filename); |
| 403 | if (idStr && fontData) { |
| 404 | std::string id(idStr); |
| 405 | LOG(VERBOSE, "THEME: Loading theme font %s\n" , idStr); |
| 406 | |
| 407 | int size = 10; |
| 408 | const char* sizeStr = xmlFont->Attribute("size" ); |
| 409 | if (sizeStr) |
| 410 | size = std::strtol(sizeStr, nullptr, 10); |
| 411 | |
| 412 | os::FontRef font = fontData->getFont(size); |
| 413 | m_themeFonts[idStr] = font; |
| 414 | |
| 415 | if (id == "default" ) |
| 416 | m_defaultFont = font; |
| 417 | else if (id == "mini" ) |
| 418 | m_miniFont = font; |
| 419 | } |
| 420 | |
| 421 | xmlFont = xmlFont->NextSiblingElement(); |
| 422 | } |
| 423 | } |
| 424 | |
| 425 | // No available font to run the program |
| 426 | if (!m_defaultFont) |
| 427 | throw base::Exception("There is no default font" ); |
| 428 | if (!m_miniFont) |
| 429 | m_miniFont = m_defaultFont; |
| 430 | |
| 431 | // Load dimension |
| 432 | { |
| 433 | TiXmlElement* xmlDim = handle |
| 434 | .FirstChild("theme" ) |
| 435 | .FirstChild("dimensions" ) |
| 436 | .FirstChild("dim" ).ToElement(); |
| 437 | while (xmlDim) { |
| 438 | std::string id = xmlDim->Attribute("id" ); |
| 439 | uint32_t value = strtol(xmlDim->Attribute("value" ), NULL, 10); |
| 440 | |
| 441 | LOG(VERBOSE, "THEME: Loading dimension %s\n" , id.c_str()); |
| 442 | |
| 443 | m_dimensions_by_id[id] = value; |
| 444 | xmlDim = xmlDim->NextSiblingElement(); |
| 445 | } |
| 446 | } |
| 447 | |
| 448 | // Load colors |
| 449 | { |
| 450 | TiXmlElement* xmlColor = handle |
| 451 | .FirstChild("theme" ) |
| 452 | .FirstChild("colors" ) |
| 453 | .FirstChild("color" ).ToElement(); |
| 454 | while (xmlColor) { |
| 455 | std::string id = xmlColor->Attribute("id" ); |
| 456 | uint32_t value = strtol(xmlColor->Attribute("value" )+1, NULL, 16); |
| 457 | gfx::Color color = gfx::rgba( |
| 458 | (value & 0xff0000) >> 16, |
| 459 | (value & 0xff00) >> 8, |
| 460 | (value & 0xff)); |
| 461 | |
| 462 | LOG(VERBOSE, "THEME: Loading color %s\n" , id.c_str()); |
| 463 | |
| 464 | m_colors_by_id[id] = color; |
| 465 | xmlColor = xmlColor->NextSiblingElement(); |
| 466 | } |
| 467 | } |
| 468 | |
| 469 | // Load parts |
| 470 | { |
| 471 | TiXmlElement* xmlPart = handle |
| 472 | .FirstChild("theme" ) |
| 473 | .FirstChild("parts" ) |
| 474 | .FirstChild("part" ).ToElement(); |
| 475 | while (xmlPart) { |
| 476 | // Get the tool-icon rectangle |
| 477 | const char* part_id = xmlPart->Attribute("id" ); |
| 478 | int x = scale*strtol(xmlPart->Attribute("x" ), nullptr, 10); |
| 479 | int y = scale*strtol(xmlPart->Attribute("y" ), nullptr, 10); |
| 480 | int w = (xmlPart->Attribute("w" ) ? scale*strtol(xmlPart->Attribute("w" ), nullptr, 10): 0); |
| 481 | int h = (xmlPart->Attribute("h" ) ? scale*strtol(xmlPart->Attribute("h" ), nullptr, 10): 0); |
| 482 | |
| 483 | LOG(VERBOSE, "THEME: Loading part %s\n" , part_id); |
| 484 | |
| 485 | SkinPartPtr part = m_parts_by_id[part_id]; |
| 486 | if (!part) |
| 487 | part = m_parts_by_id[part_id] = SkinPartPtr(new SkinPart); |
| 488 | |
| 489 | if (w > 0 && h > 0) { |
| 490 | part->setSpriteBounds(gfx::Rect(x, y, w, h)); |
| 491 | part->setBitmap(0, sliceSheet(part->bitmapRef(0), gfx::Rect(x, y, w, h))); |
| 492 | } |
| 493 | else if (xmlPart->Attribute("w1" )) { // 3x3-1 part (NW, N, NE, E, SE, S, SW, W) |
| 494 | int w1 = scale*strtol(xmlPart->Attribute("w1" ), nullptr, 10); |
| 495 | int w2 = scale*strtol(xmlPart->Attribute("w2" ), nullptr, 10); |
| 496 | int w3 = scale*strtol(xmlPart->Attribute("w3" ), nullptr, 10); |
| 497 | int h1 = scale*strtol(xmlPart->Attribute("h1" ), nullptr, 10); |
| 498 | int h2 = scale*strtol(xmlPart->Attribute("h2" ), nullptr, 10); |
| 499 | int h3 = scale*strtol(xmlPart->Attribute("h3" ), nullptr, 10); |
| 500 | |
| 501 | part->setSpriteBounds(gfx::Rect(x, y, w1+w2+w3, h1+h2+h3)); |
| 502 | part->setSlicesBounds(gfx::Rect(w1, h1, w2, h2)); |
| 503 | |
| 504 | part->setBitmap(0, sliceSheet(part->bitmapRef(0), gfx::Rect(x, y, w1, h1))); // NW |
| 505 | part->setBitmap(1, sliceSheet(part->bitmapRef(1), gfx::Rect(x+w1, y, w2, h1))); // N |
| 506 | part->setBitmap(2, sliceSheet(part->bitmapRef(2), gfx::Rect(x+w1+w2, y, w3, h1))); // NE |
| 507 | part->setBitmap(3, sliceSheet(part->bitmapRef(3), gfx::Rect(x+w1+w2, y+h1, w3, h2))); // E |
| 508 | part->setBitmap(4, sliceSheet(part->bitmapRef(4), gfx::Rect(x+w1+w2, y+h1+h2, w3, h3))); // SE |
| 509 | part->setBitmap(5, sliceSheet(part->bitmapRef(5), gfx::Rect(x+w1, y+h1+h2, w2, h3))); // S |
| 510 | part->setBitmap(6, sliceSheet(part->bitmapRef(6), gfx::Rect(x, y+h1+h2, w1, h3))); // SW |
| 511 | part->setBitmap(7, sliceSheet(part->bitmapRef(7), gfx::Rect(x, y+h1, w1, h2))); // W |
| 512 | } |
| 513 | |
| 514 | // Is it a mouse cursor? |
| 515 | if (std::strncmp(part_id, "cursor_" , 7) == 0) { |
| 516 | std::string cursorName = std::string(part_id).substr(7); |
| 517 | int focusx = scale*std::strtol(xmlPart->Attribute("focusx" ), NULL, 10); |
| 518 | int focusy = scale*std::strtol(xmlPart->Attribute("focusy" ), NULL, 10); |
| 519 | |
| 520 | LOG(VERBOSE, "THEME: Loading cursor '%s'\n" , cursorName.c_str()); |
| 521 | |
| 522 | auto it = m_cursors.find(cursorName); |
| 523 | if (it != m_cursors.end() && it->second != nullptr) { |
| 524 | delete it->second; |
| 525 | it->second = nullptr; |
| 526 | } |
| 527 | |
| 528 | os::SurfaceRef slice = sliceSheet(nullptr, gfx::Rect(x, y, w, h)); |
| 529 | Cursor* cursor = new Cursor(slice, gfx::Point(focusx, focusy)); |
| 530 | m_cursors[cursorName] = cursor; |
| 531 | |
| 532 | for (int c=0; c<kCursorTypes; ++c) { |
| 533 | if (cursorName == g_cursor_names[c]) { |
| 534 | m_standardCursors[c] = cursor; |
| 535 | break; |
| 536 | } |
| 537 | } |
| 538 | } |
| 539 | |
| 540 | xmlPart = xmlPart->NextSiblingElement(); |
| 541 | } |
| 542 | } |
| 543 | |
| 544 | // Load styles |
| 545 | { |
| 546 | TiXmlElement* xmlStyle = handle |
| 547 | .FirstChild("theme" ) |
| 548 | .FirstChild("styles" ) |
| 549 | .FirstChild("style" ).ToElement(); |
| 550 | |
| 551 | if (!xmlStyle) // Without styles? |
| 552 | throw base::Exception("There are no styles" ); |
| 553 | |
| 554 | while (xmlStyle) { |
| 555 | const char* style_id = xmlStyle->Attribute("id" ); |
| 556 | if (!style_id) { |
| 557 | throw base::Exception("<style> without 'id' attribute in '%s'\n" , |
| 558 | xml_filename.c_str()); |
| 559 | } |
| 560 | |
| 561 | const char* extends_id = xmlStyle->Attribute("extends" ); |
| 562 | const ui::Style* base = nullptr; |
| 563 | if (extends_id) |
| 564 | base = m_styles[extends_id]; |
| 565 | |
| 566 | if (backward) |
| 567 | backward->notifyStyleExistence(style_id); |
| 568 | |
| 569 | ui::Style* style = m_styles[style_id]; |
| 570 | if (!style) { |
| 571 | m_styles[style_id] = style = new ui::Style(base); |
| 572 | } |
| 573 | else { |
| 574 | *style = ui::Style(base); |
| 575 | } |
| 576 | style->setId(style_id); |
| 577 | |
| 578 | // Margin |
| 579 | { |
| 580 | const char* m = xmlStyle->Attribute("margin" ); |
| 581 | const char* l = xmlStyle->Attribute("margin-left" ); |
| 582 | const char* t = xmlStyle->Attribute("margin-top" ); |
| 583 | const char* r = xmlStyle->Attribute("margin-right" ); |
| 584 | const char* b = xmlStyle->Attribute("margin-bottom" ); |
| 585 | gfx::Border margin = ui::Style::UndefinedBorder(); |
| 586 | if (m || l) margin.left(scale*std::strtol(l ? l: m, nullptr, 10)); |
| 587 | if (m || t) margin.top(scale*std::strtol(t ? t: m, nullptr, 10)); |
| 588 | if (m || r) margin.right(scale*std::strtol(r ? r: m, nullptr, 10)); |
| 589 | if (m || b) margin.bottom(scale*std::strtol(b ? b: m, nullptr, 10)); |
| 590 | style->setMargin(margin); |
| 591 | } |
| 592 | |
| 593 | // Border |
| 594 | { |
| 595 | const char* m = xmlStyle->Attribute("border" ); |
| 596 | const char* l = xmlStyle->Attribute("border-left" ); |
| 597 | const char* t = xmlStyle->Attribute("border-top" ); |
| 598 | const char* r = xmlStyle->Attribute("border-right" ); |
| 599 | const char* b = xmlStyle->Attribute("border-bottom" ); |
| 600 | gfx::Border border = ui::Style::UndefinedBorder(); |
| 601 | if (m || l) border.left(scale*std::strtol(l ? l: m, nullptr, 10)); |
| 602 | if (m || t) border.top(scale*std::strtol(t ? t: m, nullptr, 10)); |
| 603 | if (m || r) border.right(scale*std::strtol(r ? r: m, nullptr, 10)); |
| 604 | if (m || b) border.bottom(scale*std::strtol(b ? b: m, nullptr, 10)); |
| 605 | style->setBorder(border); |
| 606 | } |
| 607 | |
| 608 | // Padding |
| 609 | { |
| 610 | const char* m = xmlStyle->Attribute("padding" ); |
| 611 | const char* l = xmlStyle->Attribute("padding-left" ); |
| 612 | const char* t = xmlStyle->Attribute("padding-top" ); |
| 613 | const char* r = xmlStyle->Attribute("padding-right" ); |
| 614 | const char* b = xmlStyle->Attribute("padding-bottom" ); |
| 615 | gfx::Border padding = ui::Style::UndefinedBorder(); |
| 616 | if (m || l) padding.left(scale*std::strtol(l ? l: m, nullptr, 10)); |
| 617 | if (m || t) padding.top(scale*std::strtol(t ? t: m, nullptr, 10)); |
| 618 | if (m || r) padding.right(scale*std::strtol(r ? r: m, nullptr, 10)); |
| 619 | if (m || b) padding.bottom(scale*std::strtol(b ? b: m, nullptr, 10)); |
| 620 | style->setPadding(padding); |
| 621 | } |
| 622 | |
| 623 | // Font |
| 624 | { |
| 625 | const char* fontId = xmlStyle->Attribute("font" ); |
| 626 | if (fontId) { |
| 627 | os::FontRef font = m_themeFonts[fontId]; |
| 628 | style->setFont(font); |
| 629 | } |
| 630 | } |
| 631 | |
| 632 | TiXmlElement* xmlLayer = xmlStyle->FirstChildElement(); |
| 633 | while (xmlLayer) { |
| 634 | const std::string layerName = xmlLayer->Value(); |
| 635 | |
| 636 | LOG(VERBOSE, "THEME: Layer %s for %s\n" , layerName.c_str(), style_id); |
| 637 | |
| 638 | ui::Style::Layer layer; |
| 639 | |
| 640 | // Layer type |
| 641 | if (layerName == "background" ) { |
| 642 | layer.setType(ui::Style::Layer::Type::kBackground); |
| 643 | } |
| 644 | else if (layerName == "background-border" ) { |
| 645 | layer.setType(ui::Style::Layer::Type::kBackgroundBorder); |
| 646 | } |
| 647 | else if (layerName == "border" ) { |
| 648 | layer.setType(ui::Style::Layer::Type::kBorder); |
| 649 | } |
| 650 | else if (layerName == "icon" ) { |
| 651 | layer.setType(ui::Style::Layer::Type::kIcon); |
| 652 | } |
| 653 | else if (layerName == "text" ) { |
| 654 | layer.setType(ui::Style::Layer::Type::kText); |
| 655 | } |
| 656 | else if (layerName == "newlayer" ) { |
| 657 | layer.setType(ui::Style::Layer::Type::kNewLayer); |
| 658 | } |
| 659 | |
| 660 | // Parse state condition |
| 661 | const char* stateValue = xmlLayer->Attribute("state" ); |
| 662 | if (stateValue) { |
| 663 | std::string state(stateValue); |
| 664 | int flags = 0; |
| 665 | if (state.find("disabled" ) != std::string::npos) flags |= ui::Style::Layer::kDisabled; |
| 666 | if (state.find("selected" ) != std::string::npos) flags |= ui::Style::Layer::kSelected; |
| 667 | if (state.find("focus" ) != std::string::npos) flags |= ui::Style::Layer::kFocus; |
| 668 | if (state.find("mouse" ) != std::string::npos) flags |= ui::Style::Layer::kMouse; |
| 669 | layer.setFlags(flags); |
| 670 | } |
| 671 | |
| 672 | // Align |
| 673 | const char* alignValue = xmlLayer->Attribute("align" ); |
| 674 | if (alignValue) { |
| 675 | std::string alignString(alignValue); |
| 676 | int align = 0; |
| 677 | if (alignString.find("left" ) != std::string::npos) align |= LEFT; |
| 678 | if (alignString.find("center" ) != std::string::npos) align |= CENTER; |
| 679 | if (alignString.find("right" ) != std::string::npos) align |= RIGHT; |
| 680 | if (alignString.find("top" ) != std::string::npos) align |= TOP; |
| 681 | if (alignString.find("middle" ) != std::string::npos) align |= MIDDLE; |
| 682 | if (alignString.find("bottom" ) != std::string::npos) align |= BOTTOM; |
| 683 | if (alignString.find("wordwrap" ) != std::string::npos) align |= WORDWRAP; |
| 684 | layer.setAlign(align); |
| 685 | } |
| 686 | |
| 687 | // Color |
| 688 | const char* colorId = xmlLayer->Attribute("color" ); |
| 689 | if (colorId) { |
| 690 | auto it = m_colors_by_id.find(colorId); |
| 691 | if (it != m_colors_by_id.end()) |
| 692 | layer.setColor(it->second); |
| 693 | else if (std::strcmp(colorId, "none" ) == 0) { |
| 694 | layer.setColor(gfx::ColorNone); |
| 695 | } |
| 696 | else { |
| 697 | throw base::Exception("Color <%s color='%s' ...> was not found in '%s'\n" , |
| 698 | layerName.c_str(), colorId, |
| 699 | xml_filename.c_str()); |
| 700 | } |
| 701 | } |
| 702 | |
| 703 | // Offset |
| 704 | const char* x = xmlLayer->Attribute("x" ); |
| 705 | const char* y = xmlLayer->Attribute("y" ); |
| 706 | if (x || y) { |
| 707 | gfx::Point offset(0, 0); |
| 708 | if (x) offset.x = std::strtol(x, nullptr, 10); |
| 709 | if (y) offset.y = std::strtol(y, nullptr, 10); |
| 710 | layer.setOffset(offset*scale); |
| 711 | } |
| 712 | |
| 713 | // Sprite sheet |
| 714 | const char* partId = xmlLayer->Attribute("part" ); |
| 715 | if (partId) { |
| 716 | auto it = m_parts_by_id.find(partId); |
| 717 | if (it != m_parts_by_id.end()) { |
| 718 | SkinPartPtr part = it->second; |
| 719 | if (part) { |
| 720 | if (layer.type() == ui::Style::Layer::Type::kIcon) |
| 721 | layer.setIcon(AddRef(part->bitmap(0))); |
| 722 | else { |
| 723 | layer.setSpriteSheet(m_sheet); |
| 724 | layer.setSpriteBounds(part->spriteBounds()); |
| 725 | layer.setSlicesBounds(part->slicesBounds()); |
| 726 | } |
| 727 | } |
| 728 | } |
| 729 | else if (std::strcmp(partId, "none" ) == 0) { |
| 730 | layer.setIcon(nullptr); |
| 731 | layer.setSpriteSheet(nullptr); |
| 732 | layer.setSpriteBounds(gfx::Rect(0, 0, 0, 0)); |
| 733 | layer.setSlicesBounds(gfx::Rect(0, 0, 0, 0)); |
| 734 | } |
| 735 | else { |
| 736 | throw base::Exception("Part <%s part='%s' ...> was not found in '%s'\n" , |
| 737 | layerName.c_str(), partId, |
| 738 | xml_filename.c_str()); |
| 739 | } |
| 740 | } |
| 741 | |
| 742 | if (layer.type() != ui::Style::Layer::Type::kNone) |
| 743 | style->addLayer(layer); |
| 744 | |
| 745 | xmlLayer = xmlLayer->NextSiblingElement(); |
| 746 | } |
| 747 | |
| 748 | xmlStyle = xmlStyle->NextSiblingElement(); |
| 749 | } |
| 750 | } |
| 751 | |
| 752 | if (backward) |
| 753 | backward->createMissingStyles(this); |
| 754 | |
| 755 | ThemeFile<SkinTheme>::updateInternals(); |
| 756 | } |
| 757 | |
| 758 | os::SurfaceRef SkinTheme::sliceSheet(os::SurfaceRef sur, const gfx::Rect& bounds) |
| 759 | { |
| 760 | if (sur && (sur->width() != bounds.w || |
| 761 | sur->height() != bounds.h)) { |
| 762 | sur = nullptr; |
| 763 | } |
| 764 | |
| 765 | if (!bounds.isEmpty()) { |
| 766 | if (!sur) |
| 767 | sur = os::instance()->makeRgbaSurface(bounds.w, bounds.h); |
| 768 | |
| 769 | os::SurfaceLock lockSrc(m_sheet.get()); |
| 770 | os::SurfaceLock lockDst(sur.get()); |
| 771 | m_sheet->blitTo(sur.get(), bounds.x, bounds.y, 0, 0, bounds.w, bounds.h); |
| 772 | |
| 773 | // The new surface is immutable because we're going to re-use the |
| 774 | // surface if we reload the theme. |
| 775 | // |
| 776 | // TODO Add sub-surfaces (SkBitmap::extractSubset()) |
| 777 | //sur->setImmutable(); |
| 778 | } |
| 779 | else { |
| 780 | ASSERT(!sur); |
| 781 | } |
| 782 | |
| 783 | return sur; |
| 784 | } |
| 785 | |
| 786 | os::Font* SkinTheme::getWidgetFont(const Widget* widget) const |
| 787 | { |
| 788 | auto skinPropery = std::static_pointer_cast<SkinProperty>(widget->getProperty(SkinProperty::Name)); |
| 789 | if (skinPropery && skinPropery->hasMiniFont()) |
| 790 | return getMiniFont(); |
| 791 | else |
| 792 | return getDefaultFont(); |
| 793 | } |
| 794 | |
| 795 | Cursor* SkinTheme::getStandardCursor(CursorType type) |
| 796 | { |
| 797 | if (type >= kFirstCursorType && type <= kLastCursorType) |
| 798 | return m_standardCursors[type]; |
| 799 | else |
| 800 | return nullptr; |
| 801 | } |
| 802 | |
| 803 | void SkinTheme::initWidget(Widget* widget) |
| 804 | { |
| 805 | #define BORDER(n) \ |
| 806 | widget->setBorder(gfx::Border(n)) |
| 807 | |
| 808 | #define BORDER4(L,T,R,B) \ |
| 809 | widget->setBorder(gfx::Border((L), (T), (R), (B))) |
| 810 | |
| 811 | const int scale = guiscale(); |
| 812 | |
| 813 | switch (widget->type()) { |
| 814 | |
| 815 | case kBoxWidget: |
| 816 | widget->setStyle(styles.box()); |
| 817 | BORDER(0); |
| 818 | widget->setChildSpacing(4 * scale); |
| 819 | break; |
| 820 | |
| 821 | case kButtonWidget: |
| 822 | widget->setStyle(styles.button()); |
| 823 | break; |
| 824 | |
| 825 | case kCheckWidget: |
| 826 | widget->setStyle(styles.checkBox()); |
| 827 | break; |
| 828 | |
| 829 | case kRadioWidget: |
| 830 | widget->setStyle(styles.radioButton()); |
| 831 | break; |
| 832 | |
| 833 | case kEntryWidget: |
| 834 | BORDER4( |
| 835 | parts.sunkenNormal()->bitmapW()->width(), |
| 836 | parts.sunkenNormal()->bitmapN()->height(), |
| 837 | parts.sunkenNormal()->bitmapE()->width(), |
| 838 | parts.sunkenNormal()->bitmapS()->height()); |
| 839 | widget->setChildSpacing(3 * scale); |
| 840 | break; |
| 841 | |
| 842 | case kGridWidget: |
| 843 | widget->setStyle(styles.grid()); |
| 844 | BORDER(0); |
| 845 | widget->setChildSpacing(4 * scale); |
| 846 | break; |
| 847 | |
| 848 | case kLabelWidget: |
| 849 | widget->setStyle(styles.label()); |
| 850 | break; |
| 851 | |
| 852 | case kLinkLabelWidget: |
| 853 | widget->setStyle(styles.link()); |
| 854 | break; |
| 855 | |
| 856 | case kListBoxWidget: |
| 857 | BORDER(0); |
| 858 | widget->setChildSpacing(0); |
| 859 | break; |
| 860 | |
| 861 | case kListItemWidget: |
| 862 | widget->setStyle(styles.listItem()); |
| 863 | break; |
| 864 | |
| 865 | case kComboBoxWidget: { |
| 866 | ComboBox* combobox = static_cast<ComboBox*>(widget); |
| 867 | Button* button = combobox->getButtonWidget(); |
| 868 | combobox->setChildSpacing(0); |
| 869 | button->setStyle(styles.comboboxButton()); |
| 870 | break; |
| 871 | } |
| 872 | |
| 873 | case kMenuWidget: |
| 874 | widget->setStyle(styles.menu()); |
| 875 | break; |
| 876 | |
| 877 | case kMenuBarWidget: |
| 878 | widget->setStyle(styles.menubar()); |
| 879 | break; |
| 880 | |
| 881 | case kMenuBoxWidget: |
| 882 | widget->setStyle(styles.menubox()); |
| 883 | break; |
| 884 | |
| 885 | case kMenuItemWidget: |
| 886 | BORDER(2 * scale); |
| 887 | widget->setChildSpacing(18 * scale); |
| 888 | break; |
| 889 | |
| 890 | case kSplitterWidget: |
| 891 | widget->setChildSpacing(3 * scale); |
| 892 | widget->setStyle(styles.splitter()); |
| 893 | break; |
| 894 | |
| 895 | case kSeparatorWidget: |
| 896 | // Horizontal bar |
| 897 | if (widget->align() & HORIZONTAL) { |
| 898 | if (dynamic_cast<MenuSeparator*>(widget)) { |
| 899 | widget->setStyle(styles.menuSeparator()); |
| 900 | BORDER(2 * scale); |
| 901 | } |
| 902 | else |
| 903 | widget->setStyle(styles.horizontalSeparator()); |
| 904 | } |
| 905 | // Vertical bar |
| 906 | else { |
| 907 | widget->setStyle(styles.verticalSeparator()); |
| 908 | } |
| 909 | break; |
| 910 | |
| 911 | case kSliderWidget: |
| 912 | widget->setStyle(styles.slider()); |
| 913 | break; |
| 914 | |
| 915 | case kTextBoxWidget: |
| 916 | widget->setChildSpacing(0); |
| 917 | widget->setStyle(styles.textboxText()); |
| 918 | break; |
| 919 | |
| 920 | case kViewWidget: |
| 921 | widget->setChildSpacing(0); |
| 922 | widget->setBgColor(colors.windowFace()); |
| 923 | widget->setStyle(styles.view()); |
| 924 | break; |
| 925 | |
| 926 | case kViewScrollbarWidget: |
| 927 | widget->setStyle(styles.scrollbar()); |
| 928 | static_cast<ScrollBar*>(widget)->setThumbStyle(styles.scrollbarThumb()); |
| 929 | break; |
| 930 | |
| 931 | case kViewViewportWidget: |
| 932 | BORDER(0); |
| 933 | widget->setChildSpacing(0); |
| 934 | break; |
| 935 | |
| 936 | case kManagerWidget: |
| 937 | widget->setStyle(styles.desktop()); |
| 938 | break; |
| 939 | |
| 940 | case kWindowWidget: |
| 941 | if (TipWindow* window = dynamic_cast<TipWindow*>(widget)) { |
| 942 | window->setStyle(styles.tooltipWindow()); |
| 943 | window->setArrowStyle(styles.tooltipWindowArrow()); |
| 944 | window->textBox()->setStyle(styles.tooltipText()); |
| 945 | } |
| 946 | else if (dynamic_cast<TransparentPopupWindow*>(widget)) { |
| 947 | widget->setStyle(styles.transparentPopupWindow()); |
| 948 | } |
| 949 | else if (dynamic_cast<PopupWindow*>(widget)) { |
| 950 | widget->setStyle(styles.popupWindow()); |
| 951 | } |
| 952 | else if (static_cast<Window*>(widget)->isDesktop()) { |
| 953 | widget->setStyle(styles.desktop()); |
| 954 | } |
| 955 | else { |
| 956 | if (widget->hasText()) { |
| 957 | widget->setStyle(styles.windowWithTitle()); |
| 958 | } |
| 959 | else { |
| 960 | widget->setStyle(styles.windowWithoutTitle()); |
| 961 | } |
| 962 | } |
| 963 | break; |
| 964 | |
| 965 | case kWindowTitleLabelWidget: |
| 966 | widget->setStyle(styles.windowTitleLabel()); |
| 967 | break; |
| 968 | |
| 969 | case kWindowCloseButtonWidget: |
| 970 | widget->setStyle(styles.windowCloseButton()); |
| 971 | break; |
| 972 | |
| 973 | default: |
| 974 | break; |
| 975 | } |
| 976 | } |
| 977 | |
| 978 | void SkinTheme::getWindowMask(Widget* widget, Region& region) |
| 979 | { |
| 980 | region = widget->bounds(); |
| 981 | } |
| 982 | |
| 983 | int SkinTheme::getScrollbarSize() |
| 984 | { |
| 985 | return dimensions.scrollbarSize(); |
| 986 | } |
| 987 | |
| 988 | gfx::Size SkinTheme::getEntryCaretSize(Widget* widget) |
| 989 | { |
| 990 | if (widget->font()->type() == os::FontType::FreeType) |
| 991 | return gfx::Size(2*guiscale(), widget->textHeight()); |
| 992 | else |
| 993 | return gfx::Size(2*guiscale(), widget->textHeight()+2*guiscale()); |
| 994 | } |
| 995 | |
| 996 | void SkinTheme::paintEntry(PaintEvent& ev) |
| 997 | { |
| 998 | Graphics* g = ev.graphics(); |
| 999 | Entry* widget = static_cast<Entry*>(ev.getSource()); |
| 1000 | gfx::Rect bounds = widget->clientBounds(); |
| 1001 | |
| 1002 | // Outside borders |
| 1003 | g->fillRect(BGCOLOR, bounds); |
| 1004 | |
| 1005 | bool isMiniLook = false; |
| 1006 | auto skinPropery = std::static_pointer_cast<SkinProperty>(widget->getProperty(SkinProperty::Name)); |
| 1007 | if (skinPropery) |
| 1008 | isMiniLook = (skinPropery->getLook() == MiniLook); |
| 1009 | |
| 1010 | drawRect(g, bounds, |
| 1011 | (widget->hasFocus() ? |
| 1012 | (isMiniLook ? parts.sunkenMiniFocused().get(): parts.sunkenFocused().get()): |
| 1013 | (isMiniLook ? parts.sunkenMiniNormal().get() : parts.sunkenNormal().get()))); |
| 1014 | |
| 1015 | drawEntryText(g, widget); |
| 1016 | } |
| 1017 | |
| 1018 | namespace { |
| 1019 | |
| 1020 | class DrawEntryTextDelegate : public os::DrawTextDelegate { |
| 1021 | public: |
| 1022 | DrawEntryTextDelegate(Entry* widget, Graphics* graphics, |
| 1023 | const gfx::Point& pos, const int h) |
| 1024 | : m_widget(widget) |
| 1025 | , m_graphics(graphics) |
| 1026 | , m_caretDrawn(false) |
| 1027 | // m_lastX is an absolute position on screen |
| 1028 | , m_lastX(pos.x+m_widget->bounds().x) |
| 1029 | , m_y(pos.y) |
| 1030 | , m_h(h) |
| 1031 | { |
| 1032 | m_widget->getEntryThemeInfo(&m_index, &m_caret, &m_state, &m_range); |
| 1033 | } |
| 1034 | |
| 1035 | int index() const { return m_index; } |
| 1036 | bool caretDrawn() const { return m_caretDrawn; } |
| 1037 | const gfx::Rect& textBounds() const { return m_textBounds; } |
| 1038 | |
| 1039 | void preProcessChar(const int index, |
| 1040 | const int codepoint, |
| 1041 | gfx::Color& fg, |
| 1042 | gfx::Color& bg, |
| 1043 | const gfx::Rect& charBounds) override { |
| 1044 | auto theme = SkinTheme::get(m_widget); |
| 1045 | |
| 1046 | // Normal text |
| 1047 | auto& colors = theme->colors; |
| 1048 | bg = ColorNone; |
| 1049 | fg = colors.text(); |
| 1050 | |
| 1051 | // Selected |
| 1052 | if ((m_index >= m_range.from) && |
| 1053 | (m_index < m_range.to)) { |
| 1054 | if (m_widget->hasFocus()) |
| 1055 | bg = colors.selected(); |
| 1056 | else |
| 1057 | bg = colors.disabled(); |
| 1058 | fg = colors.selectedText(); |
| 1059 | } |
| 1060 | |
| 1061 | // Disabled |
| 1062 | if (!m_widget->isEnabled()) { |
| 1063 | bg = ColorNone; |
| 1064 | fg = colors.disabled(); |
| 1065 | } |
| 1066 | |
| 1067 | m_bg = bg; |
| 1068 | } |
| 1069 | |
| 1070 | bool preDrawChar(const gfx::Rect& charBounds) override { |
| 1071 | m_textBounds |= charBounds; |
| 1072 | m_charStartX = charBounds.x; |
| 1073 | |
| 1074 | if (charBounds.x2()-m_widget->bounds().x < m_widget->clientBounds().x2()) { |
| 1075 | if (m_bg != ColorNone) { |
| 1076 | // Fill background e.g. needed for selected/highlighted |
| 1077 | // regions with TTF fonts where the char is smaller than the |
| 1078 | // text bounds [m_y,m_y+m_h) |
| 1079 | gfx::Rect fillThisRect(m_lastX-m_widget->bounds().x, |
| 1080 | m_y, charBounds.x2()-m_lastX, m_h); |
| 1081 | if (charBounds != fillThisRect) |
| 1082 | m_graphics->fillRect(m_bg, fillThisRect); |
| 1083 | } |
| 1084 | m_lastX = charBounds.x2(); |
| 1085 | return true; |
| 1086 | } |
| 1087 | else |
| 1088 | return false; |
| 1089 | } |
| 1090 | |
| 1091 | void postDrawChar(const gfx::Rect& charBounds) override { |
| 1092 | // Caret |
| 1093 | if (m_state && |
| 1094 | m_index == m_caret && |
| 1095 | m_widget->hasFocus() && |
| 1096 | m_widget->isEnabled()) { |
| 1097 | auto theme = SkinTheme::get(m_widget); |
| 1098 | theme->drawEntryCaret( |
| 1099 | m_graphics, m_widget, |
| 1100 | m_charStartX-m_widget->bounds().x, m_y); |
| 1101 | m_caretDrawn = true; |
| 1102 | } |
| 1103 | |
| 1104 | ++m_index; |
| 1105 | } |
| 1106 | |
| 1107 | private: |
| 1108 | Entry* m_widget; |
| 1109 | Graphics* m_graphics; |
| 1110 | int m_index; |
| 1111 | int m_caret; |
| 1112 | int m_state; |
| 1113 | Entry::Range m_range; |
| 1114 | gfx::Rect m_textBounds; |
| 1115 | bool m_caretDrawn; |
| 1116 | gfx::Color m_bg; |
| 1117 | int m_lastX; // Last position used to fill the background |
| 1118 | int m_y, m_h; |
| 1119 | int m_charStartX; |
| 1120 | }; |
| 1121 | |
| 1122 | } // anonymous namespace |
| 1123 | |
| 1124 | void SkinTheme::drawEntryText(ui::Graphics* g, ui::Entry* widget) |
| 1125 | { |
| 1126 | // Draw the text |
| 1127 | gfx::Rect bounds = widget->getEntryTextBounds(); |
| 1128 | |
| 1129 | DrawEntryTextDelegate delegate(widget, g, bounds.origin(), widget->textHeight()); |
| 1130 | int scroll = delegate.index(); |
| 1131 | |
| 1132 | const std::string& textString = widget->text(); |
| 1133 | base::utf8_decode dec(textString); |
| 1134 | auto pos = dec.pos(); |
| 1135 | for (int i=0; i<scroll && dec.next(); ++i) |
| 1136 | pos = dec.pos(); |
| 1137 | |
| 1138 | // TODO use a string_view() |
| 1139 | g->drawText(std::string(pos, textString.end()), |
| 1140 | colors.text(), ColorNone, |
| 1141 | bounds.origin(), &delegate); |
| 1142 | |
| 1143 | bounds.x += delegate.textBounds().w; |
| 1144 | |
| 1145 | // Draw suffix if there is enough space |
| 1146 | if (!widget->getSuffix().empty()) { |
| 1147 | Rect sufBounds(bounds.x, bounds.y, |
| 1148 | bounds.x2()-widget->childSpacing()*guiscale()-bounds.x, |
| 1149 | widget->textHeight()); |
| 1150 | IntersectClip clip(g, sufBounds & widget->clientChildrenBounds()); |
| 1151 | if (clip) { |
| 1152 | drawText( |
| 1153 | g, widget->getSuffix().c_str(), |
| 1154 | colors.entrySuffix(), ColorNone, |
| 1155 | widget, sufBounds, widget->align(), 0); |
| 1156 | } |
| 1157 | } |
| 1158 | |
| 1159 | // Draw caret at the end of the text |
| 1160 | if (!delegate.caretDrawn()) { |
| 1161 | gfx::Rect charBounds(bounds.x+widget->bounds().x, |
| 1162 | bounds.y+widget->bounds().y, 0, widget->textHeight()); |
| 1163 | delegate.preDrawChar(charBounds); |
| 1164 | delegate.postDrawChar(charBounds); |
| 1165 | } |
| 1166 | } |
| 1167 | |
| 1168 | void SkinTheme::paintListBox(PaintEvent& ev) |
| 1169 | { |
| 1170 | Graphics* g = ev.graphics(); |
| 1171 | |
| 1172 | g->fillRect(colors.background(), g->getClipBounds()); |
| 1173 | } |
| 1174 | |
| 1175 | void SkinTheme::(PaintEvent& ev) |
| 1176 | { |
| 1177 | Widget* widget = static_cast<Widget*>(ev.getSource()); |
| 1178 | Graphics* g = ev.graphics(); |
| 1179 | |
| 1180 | g->fillRect(BGCOLOR, g->getClipBounds()); |
| 1181 | } |
| 1182 | |
| 1183 | void SkinTheme::(ui::PaintEvent& ev) |
| 1184 | { |
| 1185 | int scale = guiscale(); |
| 1186 | Graphics* g = ev.graphics(); |
| 1187 | MenuItem* widget = static_cast<MenuItem*>(ev.getSource()); |
| 1188 | gfx::Rect bounds = widget->clientBounds(); |
| 1189 | gfx::Color fg, bg; |
| 1190 | int c, bar; |
| 1191 | |
| 1192 | // TODO ASSERT? |
| 1193 | if (!widget->parent()->parent()) |
| 1194 | return; |
| 1195 | |
| 1196 | bar = (widget->parent()->parent()->type() == kMenuBarWidget); |
| 1197 | |
| 1198 | // Colors |
| 1199 | if (!widget->isEnabled()) { |
| 1200 | fg = ColorNone; |
| 1201 | bg = colors.menuitemNormalFace(); |
| 1202 | } |
| 1203 | else { |
| 1204 | if (widget->isHighlighted()) { |
| 1205 | fg = colors.menuitemHighlightText(); |
| 1206 | bg = colors.menuitemHighlightFace(); |
| 1207 | } |
| 1208 | else if (widget->hasMouse()) { |
| 1209 | fg = colors.menuitemHotText(); |
| 1210 | bg = colors.menuitemHotFace(); |
| 1211 | } |
| 1212 | else { |
| 1213 | fg = colors.menuitemNormalText(); |
| 1214 | bg = colors.menuitemNormalFace(); |
| 1215 | } |
| 1216 | } |
| 1217 | |
| 1218 | // Background |
| 1219 | g->fillRect(bg, bounds); |
| 1220 | |
| 1221 | // Draw an indicator for selected items |
| 1222 | if (widget->isSelected()) { |
| 1223 | os::Surface* icon = |
| 1224 | (widget->isEnabled() ? |
| 1225 | parts.checkSelected()->bitmap(0): |
| 1226 | parts.checkDisabled()->bitmap(0)); |
| 1227 | |
| 1228 | int x = bounds.x+4*scale-icon->width()/2; |
| 1229 | int y = bounds.y+bounds.h/2-icon->height()/2; |
| 1230 | g->drawRgbaSurface(icon, x, y); |
| 1231 | } |
| 1232 | |
| 1233 | // Text |
| 1234 | if (bar) |
| 1235 | widget->setAlign(CENTER | MIDDLE); |
| 1236 | else |
| 1237 | widget->setAlign(LEFT | MIDDLE); |
| 1238 | |
| 1239 | Rect pos = bounds; |
| 1240 | if (!bar) |
| 1241 | pos.offset(widget->childSpacing()/2, 0); |
| 1242 | drawText(g, nullptr, fg, ColorNone, widget, pos, |
| 1243 | widget->align(), widget->mnemonic()); |
| 1244 | |
| 1245 | // For menu-box |
| 1246 | if (!bar) { |
| 1247 | // Draw the arrown (to indicate which this menu has a sub-menu) |
| 1248 | if (widget->getSubmenu()) { |
| 1249 | // Enabled |
| 1250 | if (widget->isEnabled()) { |
| 1251 | for (c=0; c<3*scale; c++) |
| 1252 | g->drawVLine(fg, |
| 1253 | bounds.x2()-3*scale-c, |
| 1254 | bounds.y+bounds.h/2-c, 2*c+1); |
| 1255 | } |
| 1256 | // Disabled |
| 1257 | else { |
| 1258 | for (c=0; c<3*scale; c++) |
| 1259 | g->drawVLine(colors.background(), |
| 1260 | bounds.x2()-3*scale-c+1, |
| 1261 | bounds.y+bounds.h/2-c+1, 2*c+1); |
| 1262 | |
| 1263 | for (c=0; c<3*scale; c++) |
| 1264 | g->drawVLine(colors.disabled(), |
| 1265 | bounds.x2()-3*scale-c, |
| 1266 | bounds.y+bounds.h/2-c, 2*c+1); |
| 1267 | } |
| 1268 | } |
| 1269 | // Draw the keyboard shortcut |
| 1270 | else if (AppMenuItem* = dynamic_cast<AppMenuItem*>(widget)) { |
| 1271 | if (appMenuItem->key() && !appMenuItem->key()->accels().empty()) { |
| 1272 | int old_align = appMenuItem->align(); |
| 1273 | |
| 1274 | pos = bounds; |
| 1275 | pos.w -= widget->childSpacing()/4; |
| 1276 | |
| 1277 | std::string buf = appMenuItem->key()->accels().front().toString(); |
| 1278 | |
| 1279 | widget->setAlign(RIGHT | MIDDLE); |
| 1280 | drawText(g, buf.c_str(), fg, ColorNone, widget, pos, widget->align(), 0); |
| 1281 | widget->setAlign(old_align); |
| 1282 | } |
| 1283 | } |
| 1284 | } |
| 1285 | } |
| 1286 | |
| 1287 | void SkinTheme::paintSlider(PaintEvent& ev) |
| 1288 | { |
| 1289 | Graphics* g = ev.graphics(); |
| 1290 | Slider* widget = static_cast<Slider*>(ev.getSource()); |
| 1291 | const Rect bounds = widget->clientBounds(); |
| 1292 | int min, max, value; |
| 1293 | |
| 1294 | // Outside borders |
| 1295 | gfx::Color bgcolor = widget->bgColor(); |
| 1296 | if (!is_transparent(bgcolor)) |
| 1297 | g->fillRect(bgcolor, bounds); |
| 1298 | |
| 1299 | widget->getSliderThemeInfo(&min, &max, &value); |
| 1300 | |
| 1301 | Rect rc = bounds; |
| 1302 | rc.shrink(widget->border()); |
| 1303 | int x; |
| 1304 | if (min != max) |
| 1305 | x = rc.x + rc.w * (value-min) / (max-min); |
| 1306 | else |
| 1307 | x = rc.x; |
| 1308 | |
| 1309 | rc = bounds; |
| 1310 | |
| 1311 | // The mini-look is used for sliders with tiny borders. |
| 1312 | bool isMiniLook = false; |
| 1313 | |
| 1314 | // The BG painter is used for sliders without a number-indicator and |
| 1315 | // customized background (e.g. RGB sliders) |
| 1316 | ISliderBgPainter* bgPainter = NULL; |
| 1317 | |
| 1318 | const auto skinPropery = std::static_pointer_cast<SkinProperty>(widget->getProperty(SkinProperty::Name)); |
| 1319 | if (skinPropery) |
| 1320 | isMiniLook = (skinPropery->getLook() == MiniLook); |
| 1321 | |
| 1322 | const auto skinSliderPropery = std::static_pointer_cast<SkinSliderProperty>(widget->getProperty(SkinSliderProperty::Name)); |
| 1323 | if (skinSliderPropery) |
| 1324 | bgPainter = skinSliderPropery->getBgPainter(); |
| 1325 | |
| 1326 | // Draw customized background |
| 1327 | if (bgPainter) { |
| 1328 | SkinPartPtr nw = parts.miniSliderEmpty(); |
| 1329 | os::Surface* thumb = |
| 1330 | (widget->hasFocus() ? parts.miniSliderThumbFocused()->bitmap(0): |
| 1331 | parts.miniSliderThumb()->bitmap(0)); |
| 1332 | |
| 1333 | // Draw background |
| 1334 | g->fillRect(BGCOLOR, rc); |
| 1335 | |
| 1336 | // Draw thumb |
| 1337 | int thumb_y = rc.y; |
| 1338 | if (rc.h > thumb->height()*3) |
| 1339 | rc.shrink(Border(0, thumb->height(), 0, 0)); |
| 1340 | |
| 1341 | // Draw borders |
| 1342 | if (rc.h > 4*guiscale()) { |
| 1343 | rc.shrink(Border(3, 0, 3, 1) * guiscale()); |
| 1344 | drawRect(g, rc, nw.get()); |
| 1345 | } |
| 1346 | |
| 1347 | // Draw background (using the customized ISliderBgPainter implementation) |
| 1348 | rc.shrink(Border(1, 1, 1, 2) * guiscale()); |
| 1349 | if (!rc.isEmpty()) |
| 1350 | bgPainter->paint(widget, g, rc); |
| 1351 | |
| 1352 | g->drawRgbaSurface(thumb, x-thumb->width()/2, thumb_y); |
| 1353 | } |
| 1354 | else { |
| 1355 | // Draw borders |
| 1356 | SkinPartPtr full_part; |
| 1357 | SkinPartPtr empty_part; |
| 1358 | |
| 1359 | if (isMiniLook) { |
| 1360 | full_part = widget->hasMouseOver() ? parts.miniSliderFullFocused(): |
| 1361 | parts.miniSliderFull(); |
| 1362 | empty_part = widget->hasMouseOver() ? parts.miniSliderEmptyFocused(): |
| 1363 | parts.miniSliderEmpty(); |
| 1364 | } |
| 1365 | else { |
| 1366 | full_part = widget->hasFocus() ? parts.sliderFullFocused(): |
| 1367 | parts.sliderFull(); |
| 1368 | empty_part = widget->hasFocus() ? parts.sliderEmptyFocused(): |
| 1369 | parts.sliderEmpty(); |
| 1370 | } |
| 1371 | |
| 1372 | if (value == min) |
| 1373 | drawRect(g, rc, empty_part.get()); |
| 1374 | else if (value == max) |
| 1375 | drawRect(g, rc, full_part.get()); |
| 1376 | else |
| 1377 | drawRect2(g, rc, x, |
| 1378 | full_part.get(), empty_part.get()); |
| 1379 | |
| 1380 | // Draw text |
| 1381 | std::string old_text = widget->text(); |
| 1382 | widget->setTextQuiet(widget->convertValueToText(value)); |
| 1383 | |
| 1384 | gfx::Rect textrc; |
| 1385 | int textAlign; |
| 1386 | calcTextInfo(widget, widget->style(), bounds, textrc, textAlign); |
| 1387 | |
| 1388 | { |
| 1389 | IntersectClip clip(g, Rect(rc.x, rc.y, x-rc.x+1, rc.h)); |
| 1390 | if (clip) { |
| 1391 | drawText(g, nullptr, |
| 1392 | colors.sliderFullText(), ColorNone, |
| 1393 | widget, textrc, textAlign, widget->mnemonic()); |
| 1394 | } |
| 1395 | } |
| 1396 | |
| 1397 | { |
| 1398 | IntersectClip clip(g, Rect(x+1, rc.y, rc.w-(x-rc.x+1), rc.h)); |
| 1399 | if (clip) { |
| 1400 | drawText(g, nullptr, |
| 1401 | colors.sliderEmptyText(), |
| 1402 | ColorNone, widget, textrc, textAlign, widget->mnemonic()); |
| 1403 | } |
| 1404 | } |
| 1405 | |
| 1406 | widget->setTextQuiet(old_text.c_str()); |
| 1407 | } |
| 1408 | } |
| 1409 | |
| 1410 | void SkinTheme::paintComboBoxEntry(ui::PaintEvent& ev) |
| 1411 | { |
| 1412 | Graphics* g = ev.graphics(); |
| 1413 | Entry* widget = static_cast<Entry*>(ev.getSource()); |
| 1414 | gfx::Rect bounds = widget->clientBounds(); |
| 1415 | |
| 1416 | // Outside borders |
| 1417 | g->fillRect(BGCOLOR, bounds); |
| 1418 | |
| 1419 | drawRect(g, bounds, |
| 1420 | (widget->hasFocus() ? |
| 1421 | parts.sunken2Focused().get(): |
| 1422 | parts.sunken2Normal().get())); |
| 1423 | |
| 1424 | drawEntryText(g, widget); |
| 1425 | } |
| 1426 | |
| 1427 | void SkinTheme::paintTextBox(ui::PaintEvent& ev) |
| 1428 | { |
| 1429 | Graphics* g = ev.graphics(); |
| 1430 | Widget* widget = static_cast<Widget*>(ev.getSource()); |
| 1431 | |
| 1432 | Theme::paintTextBoxWithStyle(g, widget); |
| 1433 | } |
| 1434 | |
| 1435 | void SkinTheme::paintViewViewport(PaintEvent& ev) |
| 1436 | { |
| 1437 | Viewport* widget = static_cast<Viewport*>(ev.getSource()); |
| 1438 | Graphics* g = ev.graphics(); |
| 1439 | gfx::Color bg = BGCOLOR; |
| 1440 | |
| 1441 | if (!is_transparent(bg)) |
| 1442 | g->fillRect(bg, widget->clientBounds()); |
| 1443 | } |
| 1444 | |
| 1445 | gfx::Color SkinTheme::getWidgetBgColor(Widget* widget) |
| 1446 | { |
| 1447 | gfx::Color c = widget->bgColor(); |
| 1448 | bool decorative = widget->isDecorative(); |
| 1449 | |
| 1450 | if (!is_transparent(c) || |
| 1451 | widget->type() == kWindowWidget) |
| 1452 | return (widget->isTransparent() ? gfx::ColorNone: c); |
| 1453 | else if (decorative) |
| 1454 | return colors.selected(); |
| 1455 | else |
| 1456 | return colors.face(); |
| 1457 | } |
| 1458 | |
| 1459 | void SkinTheme::drawText(Graphics* g, const char* t, |
| 1460 | const gfx::Color fgColor, |
| 1461 | const gfx::Color bgColor, |
| 1462 | const Widget* widget, |
| 1463 | const Rect& rc, |
| 1464 | const int textAlign, |
| 1465 | const int mnemonic) |
| 1466 | { |
| 1467 | if (t || widget->hasText()) { |
| 1468 | Rect textrc; |
| 1469 | |
| 1470 | g->setFont(AddRef(widget->font())); |
| 1471 | |
| 1472 | if (!t) |
| 1473 | t = widget->text().c_str(); |
| 1474 | |
| 1475 | textrc.setSize(g->measureUIText(t)); |
| 1476 | |
| 1477 | // Horizontally text alignment |
| 1478 | |
| 1479 | if (textAlign & RIGHT) |
| 1480 | textrc.x = rc.x + rc.w - textrc.w - 1; |
| 1481 | else if (textAlign & CENTER) |
| 1482 | textrc.x = rc.center().x - textrc.w/2; |
| 1483 | else |
| 1484 | textrc.x = rc.x; |
| 1485 | |
| 1486 | // Vertically text alignment |
| 1487 | |
| 1488 | if (textAlign & BOTTOM) |
| 1489 | textrc.y = rc.y + rc.h - textrc.h - 1; |
| 1490 | else if (textAlign & MIDDLE) |
| 1491 | textrc.y = rc.center().y - textrc.h/2; |
| 1492 | else |
| 1493 | textrc.y = rc.y; |
| 1494 | |
| 1495 | // Background |
| 1496 | if (!is_transparent(bgColor)) { |
| 1497 | if (!widget->isEnabled()) |
| 1498 | g->fillRect(bgColor, Rect(textrc).inflate(guiscale(), guiscale())); |
| 1499 | else |
| 1500 | g->fillRect(bgColor, textrc); |
| 1501 | } |
| 1502 | |
| 1503 | // Text |
| 1504 | Rect textWrap = textrc.createIntersection( |
| 1505 | // TODO add ui::Widget::getPadding() property |
| 1506 | // Rect(widget->clientBounds()).shrink(widget->border())); |
| 1507 | widget->clientBounds()).inflate(0, 1*guiscale()); |
| 1508 | |
| 1509 | IntersectClip clip(g, textWrap); |
| 1510 | if (clip) { |
| 1511 | if (!widget->isEnabled()) { |
| 1512 | // Draw white part |
| 1513 | g->drawUIText( |
| 1514 | t, |
| 1515 | colors.background(), |
| 1516 | gfx::ColorNone, |
| 1517 | textrc.origin() + Point(guiscale(), guiscale()), |
| 1518 | mnemonic); |
| 1519 | } |
| 1520 | |
| 1521 | g->drawUIText( |
| 1522 | t, |
| 1523 | (!widget->isEnabled() ? |
| 1524 | colors.disabled(): |
| 1525 | (gfx::geta(fgColor) > 0 ? fgColor : |
| 1526 | colors.text())), |
| 1527 | bgColor, textrc.origin(), |
| 1528 | mnemonic); |
| 1529 | } |
| 1530 | } |
| 1531 | } |
| 1532 | |
| 1533 | void SkinTheme::drawEntryCaret(ui::Graphics* g, Entry* widget, int x, int y) |
| 1534 | { |
| 1535 | gfx::Color color = colors.text(); |
| 1536 | int textHeight = widget->textHeight(); |
| 1537 | gfx::Size caretSize = getEntryCaretSize(widget); |
| 1538 | |
| 1539 | for (int u=x; u<x+caretSize.w; ++u) |
| 1540 | g->drawVLine(color, u, y+textHeight/2-caretSize.h/2, caretSize.h); |
| 1541 | } |
| 1542 | |
| 1543 | SkinPartPtr SkinTheme::getToolPart(const char* toolId) const |
| 1544 | { |
| 1545 | return getPartById(std::string("tool_" ) + toolId); |
| 1546 | } |
| 1547 | |
| 1548 | os::Surface* SkinTheme::getToolIcon(const char* toolId) const |
| 1549 | { |
| 1550 | SkinPartPtr part = getToolPart(toolId); |
| 1551 | if (part) |
| 1552 | return part->bitmap(0); |
| 1553 | else |
| 1554 | return nullptr; |
| 1555 | } |
| 1556 | |
| 1557 | void SkinTheme::drawRect(Graphics* g, const Rect& rc, |
| 1558 | os::Surface* nw, os::Surface* n, os::Surface* ne, |
| 1559 | os::Surface* e, os::Surface* se, os::Surface* s, |
| 1560 | os::Surface* sw, os::Surface* w) |
| 1561 | { |
| 1562 | int x, y; |
| 1563 | |
| 1564 | // Top |
| 1565 | |
| 1566 | g->drawRgbaSurface(nw, rc.x, rc.y); |
| 1567 | { |
| 1568 | IntersectClip clip(g, Rect(rc.x+nw->width(), rc.y, |
| 1569 | rc.w-nw->width()-ne->width(), rc.h)); |
| 1570 | if (clip) { |
| 1571 | for (x = rc.x+nw->width(); |
| 1572 | x < rc.x+rc.w-ne->width(); |
| 1573 | x += n->width()) { |
| 1574 | g->drawRgbaSurface(n, x, rc.y); |
| 1575 | } |
| 1576 | } |
| 1577 | } |
| 1578 | |
| 1579 | g->drawRgbaSurface(ne, rc.x+rc.w-ne->width(), rc.y); |
| 1580 | |
| 1581 | // Bottom |
| 1582 | |
| 1583 | g->drawRgbaSurface(sw, rc.x, rc.y+rc.h-sw->height()); |
| 1584 | { |
| 1585 | IntersectClip clip(g, Rect(rc.x+sw->width(), rc.y, |
| 1586 | rc.w-sw->width()-se->width(), rc.h)); |
| 1587 | if (clip) { |
| 1588 | for (x = rc.x+sw->width(); |
| 1589 | x < rc.x+rc.w-se->width(); |
| 1590 | x += s->width()) { |
| 1591 | g->drawRgbaSurface(s, x, rc.y+rc.h-s->height()); |
| 1592 | } |
| 1593 | } |
| 1594 | } |
| 1595 | |
| 1596 | g->drawRgbaSurface(se, rc.x+rc.w-se->width(), rc.y+rc.h-se->height()); |
| 1597 | { |
| 1598 | IntersectClip clip(g, Rect(rc.x, rc.y+nw->height(), |
| 1599 | rc.w, rc.h-nw->height()-sw->height())); |
| 1600 | if (clip) { |
| 1601 | // Left |
| 1602 | for (y = rc.y+nw->height(); |
| 1603 | y < rc.y+rc.h-sw->height(); |
| 1604 | y += w->height()) { |
| 1605 | g->drawRgbaSurface(w, rc.x, y); |
| 1606 | } |
| 1607 | |
| 1608 | // Right |
| 1609 | for (y = rc.y+ne->height(); |
| 1610 | y < rc.y+rc.h-se->height(); |
| 1611 | y += e->height()) { |
| 1612 | g->drawRgbaSurface(e, rc.x+rc.w-e->width(), y); |
| 1613 | } |
| 1614 | } |
| 1615 | } |
| 1616 | } |
| 1617 | |
| 1618 | void SkinTheme::drawRect(ui::Graphics* g, const gfx::Rect& rc, |
| 1619 | SkinPart* skinPart, const bool drawCenter) |
| 1620 | { |
| 1621 | Theme::drawSlices(g, m_sheet.get(), rc, |
| 1622 | skinPart->spriteBounds(), |
| 1623 | skinPart->slicesBounds(), |
| 1624 | gfx::ColorNone, |
| 1625 | drawCenter); |
| 1626 | } |
| 1627 | |
| 1628 | void SkinTheme::drawRect2(Graphics* g, const Rect& rc, int x_mid, |
| 1629 | SkinPart* nw1, SkinPart* nw2) |
| 1630 | { |
| 1631 | Rect rc2(rc.x, rc.y, x_mid-rc.x+1, rc.h); |
| 1632 | { |
| 1633 | IntersectClip clip(g, rc2); |
| 1634 | if (clip) |
| 1635 | drawRect(g, rc, nw1); |
| 1636 | } |
| 1637 | |
| 1638 | rc2.x += rc2.w; |
| 1639 | rc2.w = rc.w - rc2.w; |
| 1640 | |
| 1641 | IntersectClip clip(g, rc2); |
| 1642 | if (clip) |
| 1643 | drawRect(g, rc, nw2); |
| 1644 | } |
| 1645 | |
| 1646 | void SkinTheme::drawHline(ui::Graphics* g, const gfx::Rect& rc, SkinPart* part) |
| 1647 | { |
| 1648 | int x; |
| 1649 | |
| 1650 | for (x = rc.x; |
| 1651 | x < rc.x2()-part->size().w; |
| 1652 | x += part->size().w) { |
| 1653 | g->drawRgbaSurface(part->bitmap(0), x, rc.y); |
| 1654 | } |
| 1655 | |
| 1656 | if (x < rc.x2()) { |
| 1657 | Rect rc2(x, rc.y, rc.w-(x-rc.x), part->size().h); |
| 1658 | IntersectClip clip(g, rc2); |
| 1659 | if (clip) |
| 1660 | g->drawRgbaSurface(part->bitmap(0), x, rc.y); |
| 1661 | } |
| 1662 | } |
| 1663 | |
| 1664 | void SkinTheme::drawVline(ui::Graphics* g, const gfx::Rect& rc, SkinPart* part) |
| 1665 | { |
| 1666 | int y; |
| 1667 | |
| 1668 | for (y = rc.y; |
| 1669 | y < rc.y2()-part->size().h; |
| 1670 | y += part->size().h) { |
| 1671 | g->drawRgbaSurface(part->bitmap(0), rc.x, y); |
| 1672 | } |
| 1673 | |
| 1674 | if (y < rc.y2()) { |
| 1675 | Rect rc2(rc.x, y, part->size().w, rc.h-(y-rc.y)); |
| 1676 | IntersectClip clip(g, rc2); |
| 1677 | if (clip) |
| 1678 | g->drawRgbaSurface(part->bitmap(0), rc.x, y); |
| 1679 | } |
| 1680 | } |
| 1681 | |
| 1682 | void SkinTheme::paintProgressBar(ui::Graphics* g, const gfx::Rect& rc0, double progress) |
| 1683 | { |
| 1684 | gfx::Color border = colors.text(); |
| 1685 | border = gfx::rgba(gfx::getr(border), |
| 1686 | gfx::getg(border), |
| 1687 | gfx::getb(border), 64); |
| 1688 | g->drawRect(border, rc0); |
| 1689 | |
| 1690 | gfx::Rect rc = rc0; |
| 1691 | rc.shrink(1); |
| 1692 | |
| 1693 | int u = (int)((double)rc.w*progress); |
| 1694 | u = std::clamp(u, 0, rc.w); |
| 1695 | |
| 1696 | if (u > 0) |
| 1697 | g->fillRect(colors.selected(), gfx::Rect(rc.x, rc.y, u, rc.h)); |
| 1698 | |
| 1699 | if (1+u < rc.w) |
| 1700 | g->fillRect(colors.background(), gfx::Rect(rc.x+u, rc.y, rc.w-u, rc.h)); |
| 1701 | } |
| 1702 | |
| 1703 | std::string SkinTheme::findThemePath(const std::string& themeId) const |
| 1704 | { |
| 1705 | // First we try to find the theme on an extensions |
| 1706 | std::string path = App::instance()->extensions().themePath(themeId); |
| 1707 | if (path.empty()) { |
| 1708 | // Then we try a theme in the old themes/ folder |
| 1709 | path = base::join_path(SkinTheme::kThemesFolderName, themeId); |
| 1710 | path = base::join_path(path, "theme.xml" ); |
| 1711 | |
| 1712 | ResourceFinder rf; |
| 1713 | rf.includeDataDir(path.c_str()); |
| 1714 | if (!rf.findFirst()) |
| 1715 | return std::string(); |
| 1716 | |
| 1717 | path = base::get_file_path(rf.filename()); |
| 1718 | } |
| 1719 | return base::normalize_path(path); |
| 1720 | } |
| 1721 | |
| 1722 | } // namespace skin |
| 1723 | } // namespace app |
| 1724 | |