tb_skin.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848
  1. // ================================================================================
  2. // == This file is a part of Turbo Badger. (C) 2011-2014, Emil Segerås ==
  3. // == See tb_core.h for more information. ==
  4. // ================================================================================
  5. #include "tb_skin.h"
  6. #include "tb_system.h"
  7. #include "tb_tempbuffer.h"
  8. #include "tb_node_tree.h"
  9. #include <string.h>
  10. #include <assert.h>
  11. #include <stdio.h>
  12. namespace tb {
  13. // == Util functions ==========================================================
  14. /*TB_TEXT_ALIGN StringToTextAlign(const char *align_str)
  15. {
  16. TB_TEXT_ALIGN align = TB_TEXT_ALIGN_CENTER;
  17. if (strstr(state_str, "left")) align = TB_TEXT_ALIGN_LEFT;
  18. if (strstr(state_str, "center")) align = TB_TEXT_ALIGN_CENTER;
  19. if (strstr(state_str, "right")) align = TB_TEXT_ALIGN_RIGHT;
  20. return state;
  21. }*/
  22. SKIN_STATE StringToState(const char *state_str)
  23. {
  24. SKIN_STATE state = SKIN_STATE_NONE;
  25. if (strstr(state_str, "all")) state |= SKIN_STATE_ALL;
  26. if (strstr(state_str, "disabled")) state |= SKIN_STATE_DISABLED;
  27. if (strstr(state_str, "focused")) state |= SKIN_STATE_FOCUSED;
  28. if (strstr(state_str, "pressed")) state |= SKIN_STATE_PRESSED;
  29. if (strstr(state_str, "selected")) state |= SKIN_STATE_SELECTED;
  30. if (strstr(state_str, "hovered")) state |= SKIN_STATE_HOVERED;
  31. return state;
  32. }
  33. SKIN_ELEMENT_TYPE StringToType(const char *type_str)
  34. {
  35. if (strcmp(type_str, "StretchBox") == 0)
  36. return SKIN_ELEMENT_TYPE_STRETCH_BOX;
  37. else if (strcmp(type_str, "Image") == 0)
  38. return SKIN_ELEMENT_TYPE_IMAGE;
  39. else if (strcmp(type_str, "Stretch Image") == 0)
  40. return SKIN_ELEMENT_TYPE_STRETCH_IMAGE;
  41. else if (strcmp(type_str, "Tile") == 0)
  42. return SKIN_ELEMENT_TYPE_TILE;
  43. else if (strcmp(type_str, "StretchBorder") == 0)
  44. return SKIN_ELEMENT_TYPE_STRETCH_BORDER;
  45. TBDebugOut("Skin error: Unknown skin type!\n");
  46. return SKIN_ELEMENT_TYPE_STRETCH_BOX;
  47. }
  48. TBSkinCondition::TARGET StringToTarget(const char *target_str)
  49. {
  50. if (strcmp(target_str, "this") == 0)
  51. return TBSkinCondition::TARGET_THIS;
  52. else if (strcmp(target_str, "parent") == 0)
  53. return TBSkinCondition::TARGET_PARENT;
  54. else if (strcmp(target_str, "ancestors") == 0)
  55. return TBSkinCondition::TARGET_ANCESTORS;
  56. else if (strcmp(target_str, "prev sibling") == 0)
  57. return TBSkinCondition::TARGET_PREV_SIBLING;
  58. else if (strcmp(target_str, "next sibling") == 0)
  59. return TBSkinCondition::TARGET_NEXT_SIBLING;
  60. TBDebugOut("Skin error: Unknown target in condition!\n");
  61. return TBSkinCondition::TARGET_THIS;
  62. }
  63. TBSkinCondition::PROPERTY StringToProperty(const char *prop_str)
  64. {
  65. if (strcmp(prop_str, "skin") == 0)
  66. return TBSkinCondition::PROPERTY_SKIN;
  67. else if (strcmp(prop_str, "window active") == 0)
  68. return TBSkinCondition::PROPERTY_WINDOW_ACTIVE;
  69. else if (strcmp(prop_str, "axis") == 0)
  70. return TBSkinCondition::PROPERTY_AXIS;
  71. else if (strcmp(prop_str, "align") == 0)
  72. return TBSkinCondition::PROPERTY_ALIGN;
  73. else if (strcmp(prop_str, "id") == 0)
  74. return TBSkinCondition::PROPERTY_ID;
  75. else if (strcmp(prop_str, "state") == 0)
  76. return TBSkinCondition::PROPERTY_STATE;
  77. else if (strcmp(prop_str, "value") == 0)
  78. return TBSkinCondition::PROPERTY_VALUE;
  79. else if (strcmp(prop_str, "hover") == 0)
  80. return TBSkinCondition::PROPERTY_HOVER;
  81. else if (strcmp(prop_str, "capture") == 0)
  82. return TBSkinCondition::PROPERTY_CAPTURE;
  83. else if (strcmp(prop_str, "focus") == 0)
  84. return TBSkinCondition::PROPERTY_FOCUS;
  85. return TBSkinCondition::PROPERTY_CUSTOM;
  86. }
  87. // == TBSkinCondition =======================================================
  88. TBSkinCondition::TBSkinCondition(TARGET target, PROPERTY prop, const TBID &custom_prop, const TBID &value, TEST test)
  89. : m_target(target)
  90. , m_test(test)
  91. {
  92. m_info.prop = prop;
  93. m_info.custom_prop = custom_prop;
  94. m_info.value = value;
  95. }
  96. bool TBSkinCondition::GetCondition(TBSkinConditionContext &context) const
  97. {
  98. bool equal = context.GetCondition(m_target, m_info);
  99. return equal == (m_test == TEST_EQUAL);
  100. }
  101. // == TBSkin ================================================================
  102. TBSkin::TBSkin()
  103. : m_listener(nullptr)
  104. , m_default_disabled_opacity(0.3f)
  105. , m_default_placeholder_opacity(0.2f)
  106. , m_default_spacing(0)
  107. {
  108. g_renderer->AddListener(this);
  109. // Avoid filtering artifacts at edges when we draw fragments stretched.
  110. m_frag_manager.SetAddBorder(true);
  111. }
  112. bool TBSkin::Load(const char *skin_file, const char *override_skin_file)
  113. {
  114. if (!LoadInternal(skin_file))
  115. return false;
  116. if (override_skin_file && strlen(override_skin_file) && !LoadInternal(override_skin_file))
  117. return false;
  118. return ReloadBitmaps();
  119. }
  120. bool TBSkin::LoadInternal(const char *skin_file)
  121. {
  122. TBNode node;
  123. if (!node.ReadFile(skin_file))
  124. return false;
  125. TBTempBuffer skin_path;
  126. if (!skin_path.AppendPath(skin_file))
  127. return false;
  128. if (node.GetNode("description"))
  129. {
  130. // Check which DPI mode the dimension converter should use.
  131. // The base-dpi is the dpi in which the padding, spacing (and so on)
  132. // is specified in. If the skin supports a different DPI that is
  133. // closer to the screen DPI, all such dimensions will be scaled.
  134. int base_dpi = node.GetValueInt("description>base-dpi", 96);
  135. int supported_dpi = base_dpi;
  136. if (TBNode *supported_dpi_node = node.GetNode("description>supported-dpi"))
  137. {
  138. assert(supported_dpi_node->GetValue().IsArray() || supported_dpi_node->GetValue().GetInt() == base_dpi);
  139. if (TBValueArray *arr = supported_dpi_node->GetValue().GetArray())
  140. {
  141. int screen_dpi = TBSystem::GetDPI();
  142. int best_supported_dpi = 0;
  143. for (int i = 0; i < arr->GetLength(); i++)
  144. {
  145. int candidate_dpi = arr->GetValue(i)->GetInt();
  146. if (!best_supported_dpi || ABS(candidate_dpi - screen_dpi) < ABS(best_supported_dpi - screen_dpi))
  147. best_supported_dpi = candidate_dpi;
  148. }
  149. supported_dpi = best_supported_dpi;
  150. }
  151. }
  152. m_dim_conv.SetDPI(base_dpi, supported_dpi);
  153. }
  154. // Read skin constants
  155. if (const char *color = node.GetValueString("defaults>text-color", nullptr))
  156. m_default_text_color.SetFromString(color, strlen(color));
  157. m_default_disabled_opacity = node.GetValueFloat("defaults>disabled>opacity",
  158. m_default_disabled_opacity);
  159. m_default_placeholder_opacity = node.GetValueFloat("defaults>placeholder>opacity",
  160. m_default_placeholder_opacity);
  161. m_default_spacing = GetPxFromNode(node.GetNode("defaults>spacing"), m_default_spacing);
  162. // Iterate through all elements nodes and add skin elements or patch already
  163. // existing elements.
  164. TBNode *elements = node.GetNode("elements");
  165. if (elements)
  166. {
  167. TBNode *n = elements->GetFirstChild();
  168. while (n)
  169. {
  170. // If we have a "clone" node, clone all children from that node
  171. // into this node.
  172. while (TBNode *clone = n->GetNode("clone"))
  173. {
  174. n->Remove(clone);
  175. TBNode *clone_source = elements->GetNode(clone->GetValue().GetString());
  176. if (clone_source)
  177. n->CloneChildren(clone_source);
  178. delete clone;
  179. }
  180. // If the skin element already exist, we will call Load on it again.
  181. // This will patch the element with any new data from the node.
  182. TBID element_id(n->GetName());
  183. TBSkinElement *e = GetSkinElement(element_id);
  184. if (!e)
  185. {
  186. e = new TBSkinElement;
  187. if (!e)
  188. return false;
  189. m_elements.Add(element_id, e);
  190. }
  191. e->Load(n, this, skin_path.GetData());
  192. if (m_listener)
  193. m_listener->OnSkinElementLoaded(this, e, n);
  194. n = n->GetNext();
  195. }
  196. }
  197. return true;
  198. }
  199. void TBSkin::UnloadBitmaps()
  200. {
  201. // Unset all bitmap pointers.
  202. TBHashTableIteratorOf<TBSkinElement> it(&m_elements);
  203. while (TBSkinElement *element = it.GetNextContent())
  204. element->bitmap = nullptr;
  205. // Clear all fragments and bitmaps.
  206. m_frag_manager.Clear();
  207. }
  208. bool TBSkin::ReloadBitmaps()
  209. {
  210. UnloadBitmaps();
  211. bool success = ReloadBitmapsInternal();
  212. // Create all bitmaps for the bitmap fragment maps
  213. if (success)
  214. success = m_frag_manager.ValidateBitmaps();
  215. #ifdef TB_RUNTIME_DEBUG_INFO
  216. TBStr info;
  217. info.SetFormatted("Skin loaded using %d bitmaps.\n", m_frag_manager.GetNumMaps());
  218. TBDebugOut(info);
  219. #endif
  220. return success;
  221. }
  222. bool TBSkin::ReloadBitmapsInternal()
  223. {
  224. // Load all bitmap files into new bitmap fragments.
  225. TBTempBuffer filename_dst_DPI;
  226. bool success = true;
  227. TBHashTableIteratorOf<TBSkinElement> it(&m_elements);
  228. while (TBSkinElement *element = it.GetNextContent())
  229. {
  230. if (!element->bitmap_file.IsEmpty())
  231. {
  232. assert(!element->bitmap);
  233. // FIX: dedicated_map is not needed for all backends (only deprecated fixed function GL)
  234. bool dedicated_map = element->type == SKIN_ELEMENT_TYPE_TILE;
  235. // Try to load bitmap fragment in the destination DPI (F.ex "foo.png" becomes "[email protected]")
  236. int bitmap_dpi = m_dim_conv.GetSrcDPI();
  237. if (m_dim_conv.NeedConversion())
  238. {
  239. m_dim_conv.GetDstDPIFilename(element->bitmap_file, &filename_dst_DPI);
  240. element->bitmap = m_frag_manager.GetFragmentFromFile(filename_dst_DPI.GetData(), dedicated_map);
  241. if (element->bitmap)
  242. bitmap_dpi = m_dim_conv.GetDstDPI();
  243. }
  244. element->SetBitmapDPI(m_dim_conv, bitmap_dpi);
  245. // If we still have no bitmap fragment, load from default file.
  246. if (!element->bitmap)
  247. element->bitmap = m_frag_manager.GetFragmentFromFile(element->bitmap_file, dedicated_map);
  248. if (!element->bitmap)
  249. success = false;
  250. }
  251. }
  252. return success;
  253. }
  254. TBSkin::~TBSkin()
  255. {
  256. g_renderer->RemoveListener(this);
  257. }
  258. TBSkinElement *TBSkin::GetSkinElement(const TBID &skin_id) const
  259. {
  260. if (!skin_id)
  261. return nullptr;
  262. return m_elements.Get(skin_id);
  263. }
  264. TBSkinElement *TBSkin::GetSkinElementStrongOverride(const TBID &skin_id, SKIN_STATE state, TBSkinConditionContext &context) const
  265. {
  266. if (TBSkinElement *skin_element = GetSkinElement(skin_id))
  267. {
  268. // Avoid eternal recursion when overrides refer to elements referring back.
  269. if (skin_element->is_getting)
  270. return nullptr;
  271. skin_element->is_getting = true;
  272. // Check if there's any strong overrides for this element with the given state.
  273. TBSkinElementState *override_state = skin_element->m_strong_override_elements.GetStateElement(state, context);
  274. if (override_state)
  275. {
  276. if (TBSkinElement *override_element = GetSkinElementStrongOverride(override_state->element_id, state, context))
  277. {
  278. skin_element->is_getting = false;
  279. return override_element;
  280. }
  281. }
  282. skin_element->is_getting = false;
  283. return skin_element;
  284. }
  285. return nullptr;
  286. }
  287. TBSkinElement *TBSkin::PaintSkin(const TBRect &dst_rect, const TBID &skin_id, SKIN_STATE state, TBSkinConditionContext &context)
  288. {
  289. return PaintSkin(dst_rect, GetSkinElement(skin_id), state, context);
  290. }
  291. TBSkinElement *TBSkin::PaintSkin(const TBRect &dst_rect, TBSkinElement *element, SKIN_STATE state, TBSkinConditionContext &context)
  292. {
  293. if (!element || element->is_painting)
  294. return nullptr;
  295. // Avoid potential endless recursion in evil skins
  296. element->is_painting = true;
  297. // Return the override if we have one.
  298. TBSkinElement *return_element = element;
  299. TB_IF_DEBUG(bool paint_error_highlight = false);
  300. // If there's any override for this state, paint it.
  301. TBSkinElementState *override_state = element->m_override_elements.GetStateElement(state, context);
  302. if (override_state)
  303. {
  304. if (TBSkinElement *used_override = PaintSkin(dst_rect, override_state->element_id, state, context))
  305. return_element = used_override;
  306. else
  307. {
  308. TB_IF_DEBUG(paint_error_highlight = true);
  309. TBDebugOut("Skin error: The skin references a missing element, or has a reference loop!\n");
  310. // Fall back to the standard skin.
  311. override_state = nullptr;
  312. }
  313. }
  314. // If there was no override, paint the standard skin element.
  315. if (!override_state)
  316. PaintElement(dst_rect, element);
  317. // Paint all child elements that matches the state (or should be painted for all states)
  318. if (element->m_child_elements.HasStateElements())
  319. {
  320. const TBSkinElementState *state_element = element->m_child_elements.GetFirstElement();
  321. while (state_element)
  322. {
  323. if (state_element->IsMatch(state, context))
  324. PaintSkin(dst_rect, state_element->element_id, state_element->state & state, context);
  325. state_element = state_element->GetNext();
  326. }
  327. }
  328. // Paint ugly rectangles on invalid skin elements in debug builds.
  329. TB_IF_DEBUG(if (paint_error_highlight) g_renderer->DrawRect(dst_rect.Expand(1, 1), TBColor(255, 205, 0)));
  330. TB_IF_DEBUG(if (paint_error_highlight) g_renderer->DrawRect(dst_rect.Shrink(1, 1), TBColor(255, 0, 0)));
  331. element->is_painting = false;
  332. return return_element;
  333. }
  334. void TBSkin::PaintSkinOverlay(const TBRect &dst_rect, TBSkinElement *element, SKIN_STATE state, TBSkinConditionContext &context)
  335. {
  336. if (!element || element->is_painting)
  337. return;
  338. // Avoid potential endless recursion in evil skins
  339. element->is_painting = true;
  340. // Paint all overlay elements that matches the state (or should be painted for all states)
  341. const TBSkinElementState *state_element = element->m_overlay_elements.GetFirstElement();
  342. while (state_element)
  343. {
  344. if (state_element->IsMatch(state, context))
  345. PaintSkin(dst_rect, state_element->element_id, state_element->state & state, context);
  346. state_element = state_element->GetNext();
  347. }
  348. element->is_painting = false;
  349. }
  350. void TBSkin::PaintElement(const TBRect &dst_rect, TBSkinElement *element)
  351. {
  352. PaintElementBGColor(dst_rect, element);
  353. if (!element->bitmap)
  354. return;
  355. if (element->type == SKIN_ELEMENT_TYPE_IMAGE)
  356. PaintElementImage(dst_rect, element);
  357. else if (element->type == SKIN_ELEMENT_TYPE_TILE)
  358. PaintElementTile(dst_rect, element);
  359. else if (element->type == SKIN_ELEMENT_TYPE_STRETCH_IMAGE || element->cut == 0)
  360. PaintElementStretchImage(dst_rect, element);
  361. else if (element->type == SKIN_ELEMENT_TYPE_STRETCH_BORDER)
  362. PaintElementStretchBox(dst_rect, element, false);
  363. else
  364. PaintElementStretchBox(dst_rect, element, true);
  365. }
  366. TBRect TBSkin::GetFlippedRect(const TBRect &src_rect, TBSkinElement *element) const
  367. {
  368. // Turning the source rect "inside out" will flip the result when rendered.
  369. TBRect tmp_rect = src_rect;
  370. if (element->flip_x)
  371. {
  372. tmp_rect.x += tmp_rect.w;
  373. tmp_rect.w = -tmp_rect.w;
  374. }
  375. if (element->flip_y)
  376. {
  377. tmp_rect.y += tmp_rect.h;
  378. tmp_rect.h = -tmp_rect.h;
  379. }
  380. return tmp_rect;
  381. }
  382. void TBSkin::PaintElementBGColor(const TBRect &dst_rect, TBSkinElement *element)
  383. {
  384. if (element->bg_color == 0)
  385. return;
  386. g_renderer->DrawRectFill(dst_rect, element->bg_color);
  387. }
  388. void TBSkin::PaintElementImage(const TBRect &dst_rect, TBSkinElement *element)
  389. {
  390. TBRect src_rect(0, 0, element->bitmap->Width(), element->bitmap->Height());
  391. TBRect rect = dst_rect.Expand(element->expand, element->expand);
  392. rect.Set(rect.x + element->img_ofs_x + (rect.w - src_rect.w) * element->img_position_x / 100,
  393. rect.y + element->img_ofs_y + (rect.h - src_rect.h) * element->img_position_y / 100,
  394. src_rect.w, src_rect.h);
  395. g_renderer->DrawBitmap(rect, GetFlippedRect(src_rect, element), element->bitmap);
  396. }
  397. void TBSkin::PaintElementTile(const TBRect &dst_rect, TBSkinElement *element)
  398. {
  399. TBRect rect = dst_rect.Expand(element->expand, element->expand);
  400. g_renderer->DrawBitmapTile(rect, element->bitmap->GetBitmap());
  401. }
  402. void TBSkin::PaintElementStretchImage(const TBRect &dst_rect, TBSkinElement *element)
  403. {
  404. if (dst_rect.IsEmpty())
  405. return;
  406. TBRect rect = dst_rect.Expand(element->expand, element->expand);
  407. TBRect src_rect = GetFlippedRect(TBRect(0, 0, element->bitmap->Width(), element->bitmap->Height()), element);
  408. g_renderer->DrawBitmap(rect, src_rect, element->bitmap);
  409. }
  410. void TBSkin::PaintElementStretchBox(const TBRect &dst_rect, TBSkinElement *element, bool fill_center)
  411. {
  412. if (dst_rect.IsEmpty())
  413. return;
  414. TBRect rect = dst_rect.Expand(element->expand, element->expand);
  415. // Stretch the dst_cut (if rect is smaller than the skin size)
  416. // FIX: the expand should also be stretched!
  417. int cut = element->cut;
  418. int dst_cut_w = MIN(cut, rect.w / 2);
  419. int dst_cut_h = MIN(cut, rect.h / 2);
  420. int bw = element->bitmap->Width();
  421. int bh = element->bitmap->Height();
  422. bool has_left_right_edges = rect.h > dst_cut_h * 2;
  423. bool has_top_bottom_edges = rect.w > dst_cut_w * 2;
  424. rect = GetFlippedRect(rect, element);
  425. if (element->flip_x)
  426. dst_cut_w = -dst_cut_w;
  427. if (element->flip_y)
  428. dst_cut_h = -dst_cut_h;
  429. // Corners
  430. g_renderer->DrawBitmap(TBRect(rect.x, rect.y, dst_cut_w, dst_cut_h), TBRect(0, 0, cut, cut), element->bitmap);
  431. g_renderer->DrawBitmap(TBRect(rect.x + rect.w - dst_cut_w, rect.y, dst_cut_w, dst_cut_h), TBRect(bw - cut, 0, cut, cut), element->bitmap);
  432. g_renderer->DrawBitmap(TBRect(rect.x, rect.y + rect.h - dst_cut_h, dst_cut_w, dst_cut_h), TBRect(0, bh - cut, cut, cut), element->bitmap);
  433. g_renderer->DrawBitmap(TBRect(rect.x + rect.w - dst_cut_w, rect.y + rect.h - dst_cut_h, dst_cut_w, dst_cut_h), TBRect(bw - cut, bh - cut, cut, cut), element->bitmap);
  434. // Left & right edge
  435. if (has_left_right_edges)
  436. {
  437. g_renderer->DrawBitmap(TBRect(rect.x, rect.y + dst_cut_h, dst_cut_w, rect.h - dst_cut_h * 2), TBRect(0, cut, cut, bh - cut * 2), element->bitmap);
  438. g_renderer->DrawBitmap(TBRect(rect.x + rect.w - dst_cut_w, rect.y + dst_cut_h, dst_cut_w, rect.h - dst_cut_h * 2), TBRect(bw - cut, cut, cut, bh - cut * 2), element->bitmap);
  439. }
  440. // Top & bottom edge
  441. if (has_top_bottom_edges)
  442. {
  443. g_renderer->DrawBitmap(TBRect(rect.x + dst_cut_w, rect.y, rect.w - dst_cut_w * 2, dst_cut_h), TBRect(cut, 0, bw - cut * 2, cut), element->bitmap);
  444. g_renderer->DrawBitmap(TBRect(rect.x + dst_cut_w, rect.y + rect.h - dst_cut_h, rect.w - dst_cut_w * 2, dst_cut_h), TBRect(cut, bh - cut, bw - cut * 2, cut), element->bitmap);
  445. }
  446. // Center
  447. if (fill_center && has_top_bottom_edges && has_left_right_edges)
  448. g_renderer->DrawBitmap(TBRect(rect.x + dst_cut_w, rect.y + dst_cut_h, rect.w - dst_cut_w * 2, rect.h - dst_cut_h * 2), TBRect(cut, cut, bw - cut * 2, bh - cut * 2), element->bitmap);
  449. }
  450. #ifdef TB_RUNTIME_DEBUG_INFO
  451. void TBSkin::Debug()
  452. {
  453. m_frag_manager.Debug();
  454. }
  455. #endif // TB_RUNTIME_DEBUG_INFO
  456. void TBSkin::OnContextLost()
  457. {
  458. // We could simply do: m_frag_manager.DeleteBitmaps() and then all bitmaps
  459. // would be recreated automatically when needed. But because it's easy,
  460. // we unload everything so we save some memory (by not keeping any image
  461. // data around).
  462. UnloadBitmaps();
  463. }
  464. void TBSkin::OnContextRestored()
  465. {
  466. // Reload bitmaps (since we unloaded everything in OnContextLost())
  467. ReloadBitmaps();
  468. }
  469. int TBSkin::GetPxFromNode(TBNode *node, int def_value) const
  470. {
  471. return node ? m_dim_conv.GetPxFromValue(&node->GetValue(), def_value) : def_value;
  472. }
  473. // == TBSkinElement =========================================================
  474. TBSkinElement::TBSkinElement()
  475. : bitmap(nullptr), cut(0), expand(0), type(SKIN_ELEMENT_TYPE_STRETCH_BOX)
  476. , is_painting(false), is_getting(false)
  477. , padding_left(0), padding_top(0), padding_right(0), padding_bottom(0)
  478. , width(SKIN_VALUE_NOT_SPECIFIED), height(SKIN_VALUE_NOT_SPECIFIED)
  479. , pref_width(SKIN_VALUE_NOT_SPECIFIED), pref_height(SKIN_VALUE_NOT_SPECIFIED)
  480. , min_width(SKIN_VALUE_NOT_SPECIFIED), min_height(SKIN_VALUE_NOT_SPECIFIED)
  481. , max_width(SKIN_VALUE_NOT_SPECIFIED), max_height(SKIN_VALUE_NOT_SPECIFIED)
  482. , spacing(SKIN_VALUE_NOT_SPECIFIED)
  483. , content_ofs_x(0), content_ofs_y(0)
  484. , img_ofs_x(0), img_ofs_y(0)
  485. , img_position_x(50), img_position_y(50)
  486. , flip_x(0), flip_y(0), opacity(1.f)
  487. , text_color(0, 0, 0, 0)
  488. , bg_color(0, 0, 0, 0)
  489. , bitmap_dpi(0)
  490. {
  491. }
  492. TBSkinElement::~TBSkinElement()
  493. {
  494. }
  495. int TBSkinElement::GetIntrinsicMinWidth() const
  496. {
  497. // Sizes below the skin cut size would start to shrink the skin below pretty,
  498. // so assume that's the default minimum size if it's not specified (minus expansion)
  499. return cut * 2 - expand * 2;
  500. }
  501. int TBSkinElement::GetIntrinsicMinHeight() const
  502. {
  503. // Sizes below the skin cut size would start to shrink the skin below pretty,
  504. // so assume that's the default minimum size if it's not specified (minus expansion)
  505. return cut * 2 - expand * 2;
  506. }
  507. int TBSkinElement::GetIntrinsicWidth() const
  508. {
  509. if (width != SKIN_VALUE_NOT_SPECIFIED)
  510. return width;
  511. if (bitmap)
  512. return bitmap->Width() - expand * 2;
  513. // FIX: We may want to check child elements etc.
  514. return SKIN_VALUE_NOT_SPECIFIED;
  515. }
  516. int TBSkinElement::GetIntrinsicHeight() const
  517. {
  518. if (height != SKIN_VALUE_NOT_SPECIFIED)
  519. return height;
  520. if (bitmap)
  521. return bitmap->Height() - expand * 2;
  522. // FIX: We may want to check child elements etc.
  523. return SKIN_VALUE_NOT_SPECIFIED;
  524. }
  525. void TBSkinElement::SetBitmapDPI(const TBDimensionConverter &dim_conv, int bitmap_dpi)
  526. {
  527. if (this->bitmap_dpi)
  528. {
  529. // We have already applied the modifications so abort. This may
  530. // happen when we reload bitmaps without reloading the skin.
  531. return;
  532. }
  533. if (dim_conv.NeedConversion())
  534. {
  535. if (bitmap_dpi == dim_conv.GetDstDPI())
  536. {
  537. // The bitmap was loaded in a different DPI than the base DPI so
  538. // we must scale the bitmap properties.
  539. expand = expand * dim_conv.GetDstDPI() / dim_conv.GetSrcDPI();
  540. cut = cut * dim_conv.GetDstDPI() / dim_conv.GetSrcDPI();
  541. }
  542. else
  543. {
  544. // The bitmap was loaded in the base DPI and we need to scale it.
  545. // Apply the DPI conversion to the skin element scale factor.
  546. // FIX: For this to work well, we would need to apply scale to both
  547. // image and all the other types of drawing too.
  548. // scale_x = scale_x * dim_conv.GetDstDPI() / dim_conv.GetSrcDPI();
  549. // scale_y = scale_y * dim_conv.GetDstDPI() / dim_conv.GetSrcDPI();
  550. }
  551. }
  552. this->bitmap_dpi = bitmap_dpi;
  553. }
  554. bool TBSkinElement::HasState(SKIN_STATE state, TBSkinConditionContext &context)
  555. {
  556. return m_override_elements.GetStateElement(state, context, TBSkinElementState::MATCH_RULE_ONLY_SPECIFIC_STATE) ||
  557. m_child_elements.GetStateElement(state, context, TBSkinElementState::MATCH_RULE_ONLY_SPECIFIC_STATE) ||
  558. m_overlay_elements.GetStateElement(state, context, TBSkinElementState::MATCH_RULE_ONLY_SPECIFIC_STATE);
  559. }
  560. void TBSkinElement::Load(TBNode *n, TBSkin *skin, const char *skin_path)
  561. {
  562. if (const char *bitmap = n->GetValueString("bitmap", nullptr))
  563. {
  564. bitmap_file.Clear();
  565. bitmap_file.Append(skin_path);
  566. bitmap_file.Append(bitmap);
  567. }
  568. // Note: Always read cut and expand as pixels. These values might later be
  569. // recalculated depending on the DPI the bitmaps are available in.
  570. cut = n->GetValueInt("cut", cut);
  571. expand = n->GetValueInt("expand", expand);
  572. name.Set(n->GetName());
  573. id.Set(n->GetName());
  574. const TBDimensionConverter *dim_conv = skin->GetDimensionConverter();
  575. if (TBNode *padding_node = n->GetNode("padding"))
  576. {
  577. TBValue &val = padding_node->GetValue();
  578. if (val.GetArrayLength() == 4)
  579. {
  580. padding_top = dim_conv->GetPxFromValue(val.GetArray()->GetValue(0), 0);
  581. padding_right = dim_conv->GetPxFromValue(val.GetArray()->GetValue(1), 0);
  582. padding_bottom = dim_conv->GetPxFromValue(val.GetArray()->GetValue(2), 0);
  583. padding_left = dim_conv->GetPxFromValue(val.GetArray()->GetValue(3), 0);
  584. }
  585. else if (val.GetArrayLength() == 2)
  586. {
  587. padding_top = padding_bottom = dim_conv->GetPxFromValue(val.GetArray()->GetValue(0), 0);
  588. padding_left = padding_right = dim_conv->GetPxFromValue(val.GetArray()->GetValue(1), 0);
  589. }
  590. else
  591. {
  592. padding_top = padding_right = padding_bottom = padding_left = dim_conv->GetPxFromValue(&val, 0);
  593. }
  594. }
  595. width = skin->GetPxFromNode(n->GetNode("width"), width);
  596. height = skin->GetPxFromNode(n->GetNode("height"), height);
  597. pref_width = skin->GetPxFromNode(n->GetNode("pref-width"), pref_width);
  598. pref_height = skin->GetPxFromNode(n->GetNode("pref-height"), pref_height);
  599. min_width = skin->GetPxFromNode(n->GetNode("min-width"), min_width);
  600. min_height = skin->GetPxFromNode(n->GetNode("min-height"), min_height);
  601. max_width = skin->GetPxFromNode(n->GetNode("max-width"), max_width);
  602. max_height = skin->GetPxFromNode(n->GetNode("max-height"), max_height);
  603. spacing = skin->GetPxFromNode(n->GetNode("spacing"), spacing);
  604. content_ofs_x = skin->GetPxFromNode(n->GetNode("content-ofs-x"), content_ofs_x);
  605. content_ofs_y = skin->GetPxFromNode(n->GetNode("content-ofs-y"), content_ofs_y);
  606. img_position_x = n->GetValueInt("img-position-x", img_position_x);
  607. img_position_y = n->GetValueInt("img-position-y", img_position_y);
  608. img_ofs_x = skin->GetPxFromNode(n->GetNode("img-ofs-x"), img_ofs_x);
  609. img_ofs_y = skin->GetPxFromNode(n->GetNode("img-ofs-y"), img_ofs_y);
  610. flip_x = n->GetValueInt("flip-x", flip_x);
  611. flip_y = n->GetValueInt("flip-y", flip_y);
  612. opacity = n->GetValueFloat("opacity", opacity);
  613. if (const char *color = n->GetValueString("text-color", nullptr))
  614. text_color.SetFromString(color, strlen(color));
  615. if (const char *color = n->GetValueString("background-color", nullptr))
  616. bg_color.SetFromString(color, strlen(color));
  617. if (const char *type_str = n->GetValueString("type", nullptr))
  618. type = StringToType(type_str);
  619. // Create all state elements
  620. m_override_elements.Load(n->GetNode("overrides"));
  621. m_strong_override_elements.Load(n->GetNode("strong-overrides"));
  622. m_child_elements.Load(n->GetNode("children"));
  623. m_overlay_elements.Load(n->GetNode("overlays"));
  624. }
  625. // == TBSkinElementState ====================================================
  626. bool TBSkinElementState::IsMatch(SKIN_STATE state, TBSkinConditionContext &context, MATCH_RULE rule) const
  627. {
  628. if (rule == MATCH_RULE_ONLY_SPECIFIC_STATE && this->state == SKIN_STATE_ALL)
  629. return false;
  630. if ((state & this->state) || this->state == SKIN_STATE_ALL)
  631. {
  632. for (TBSkinCondition *condition = conditions.GetFirst(); condition; condition = condition->GetNext())
  633. if (!condition->GetCondition(context))
  634. return false;
  635. return true;
  636. }
  637. return false;
  638. }
  639. bool TBSkinElementState::IsExactMatch(SKIN_STATE state, TBSkinConditionContext &context, MATCH_RULE rule) const
  640. {
  641. if (rule == MATCH_RULE_ONLY_SPECIFIC_STATE && this->state == SKIN_STATE_ALL)
  642. return false;
  643. if (state == this->state || this->state == SKIN_STATE_ALL)
  644. {
  645. for (TBSkinCondition *condition = conditions.GetFirst(); condition; condition = condition->GetNext())
  646. if (!condition->GetCondition(context))
  647. return false;
  648. return true;
  649. }
  650. return false;
  651. }
  652. // == TBSkinElementStateList ==================================================
  653. TBSkinElementStateList::~TBSkinElementStateList()
  654. {
  655. while (TBSkinElementState *state = m_state_elements.GetFirst())
  656. {
  657. m_state_elements.Remove(state);
  658. delete state;
  659. }
  660. }
  661. TBSkinElementState *TBSkinElementStateList::GetStateElement(SKIN_STATE state, TBSkinConditionContext &context, TBSkinElementState::MATCH_RULE rule) const
  662. {
  663. // First try to get a state element with a exact match to the current state
  664. if (TBSkinElementState *element_state = GetStateElementExactMatch(state, context, rule))
  665. return element_state;
  666. // No exact state match. Get a state with a partly match if there is one.
  667. TBSkinElementState *state_element = m_state_elements.GetFirst();
  668. while (state_element)
  669. {
  670. if (state_element->IsMatch(state, context, rule))
  671. return state_element;
  672. state_element = state_element->GetNext();
  673. }
  674. return nullptr;
  675. }
  676. TBSkinElementState *TBSkinElementStateList::GetStateElementExactMatch(SKIN_STATE state, TBSkinConditionContext &context, TBSkinElementState::MATCH_RULE rule) const
  677. {
  678. TBSkinElementState *state_element = m_state_elements.GetFirst();
  679. while (state_element)
  680. {
  681. if (state_element->IsExactMatch(state, context, rule))
  682. return state_element;
  683. state_element = state_element->GetNext();
  684. }
  685. return nullptr;
  686. }
  687. void TBSkinElementStateList::Load(TBNode *n)
  688. {
  689. if (!n)
  690. return;
  691. // For each node, create a new state element.
  692. TBNode *element_node = n->GetFirstChild();
  693. while (element_node)
  694. {
  695. TBSkinElementState *state = new TBSkinElementState;
  696. if (!state)
  697. return;
  698. // By default, a state element applies to all combinations of states
  699. state->state = SKIN_STATE_ALL;
  700. state->element_id.Set(element_node->GetValue().GetString());
  701. // Loop through all nodes, read state and create all found conditions.
  702. for (TBNode *condition_node = element_node->GetFirstChild(); condition_node; condition_node = condition_node->GetNext())
  703. {
  704. if (strcmp(condition_node->GetName(), "state") == 0)
  705. state->state = StringToState(condition_node->GetValue().GetString());
  706. else if (strcmp(condition_node->GetName(), "condition") == 0)
  707. {
  708. TBSkinCondition::TARGET target = StringToTarget(condition_node->GetValueString("target", ""));
  709. const char *prop_str = condition_node->GetValueString("property", "");
  710. TBSkinCondition::PROPERTY prop = StringToProperty(prop_str);
  711. TBID custom_prop;
  712. if (prop == TBSkinCondition::PROPERTY_CUSTOM)
  713. custom_prop.Set(prop_str);
  714. TBID value;
  715. if (TBNode *value_n = condition_node->GetNode("value"))
  716. {
  717. // Set the it to number or string. If it's a state, we must first convert the
  718. // state string to the SKIN_STATE state combo.
  719. if (prop == TBSkinCondition::PROPERTY_STATE)
  720. value.Set(StringToState(value_n->GetValue().GetString()));
  721. else if (value_n->GetValue().IsString())
  722. value.Set(value_n->GetValue().GetString());
  723. else
  724. value.Set(value_n->GetValue().GetInt());
  725. }
  726. TBSkinCondition::TEST test = TBSkinCondition::TEST_EQUAL;
  727. if (const char *test_str = condition_node->GetValueString("test", nullptr))
  728. {
  729. if (strcmp(test_str, "!=") == 0)
  730. test = TBSkinCondition::TEST_NOT_EQUAL;
  731. }
  732. if (TBSkinCondition *condition = new TBSkinCondition(target, prop, custom_prop, value, test))
  733. state->conditions.AddLast(condition);
  734. }
  735. }
  736. // State is reado to add
  737. m_state_elements.AddLast(state);
  738. element_node = element_node->GetNext();
  739. }
  740. }
  741. }; // namespace tb