The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3036 lines
119KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. namespace LookAndFeelHelpers
  22. {
  23. static Colour createBaseColour (Colour buttonColour,
  24. bool hasKeyboardFocus,
  25. bool isMouseOverButton,
  26. bool isButtonDown) noexcept
  27. {
  28. const float sat = hasKeyboardFocus ? 1.3f : 0.9f;
  29. const Colour baseColour (buttonColour.withMultipliedSaturation (sat));
  30. if (isButtonDown) return baseColour.contrasting (0.2f);
  31. if (isMouseOverButton) return baseColour.contrasting (0.1f);
  32. return baseColour;
  33. }
  34. static TextLayout layoutTooltipText (const String& text, Colour colour) noexcept
  35. {
  36. const float tooltipFontSize = 13.0f;
  37. const int maxToolTipWidth = 400;
  38. AttributedString s;
  39. s.setJustification (Justification::centred);
  40. s.append (text, Font (tooltipFontSize, Font::bold), colour);
  41. TextLayout tl;
  42. tl.createLayoutWithBalancedLineLengths (s, (float) maxToolTipWidth);
  43. return tl;
  44. }
  45. }
  46. //==============================================================================
  47. LookAndFeel_V2::LookAndFeel_V2()
  48. {
  49. // initialise the standard set of colours..
  50. const uint32 textButtonColour = 0xffbbbbff;
  51. const uint32 textHighlightColour = 0x401111ee;
  52. const uint32 standardOutlineColour = 0xb2808080;
  53. static const uint32 standardColours[] =
  54. {
  55. TextButton::buttonColourId, textButtonColour,
  56. TextButton::buttonOnColourId, 0xff4444ff,
  57. TextButton::textColourOnId, 0xff000000,
  58. TextButton::textColourOffId, 0xff000000,
  59. ToggleButton::textColourId, 0xff000000,
  60. ToggleButton::tickColourId, 0xff000000,
  61. ToggleButton::tickDisabledColourId, 0xff808080,
  62. TextEditor::backgroundColourId, 0xffffffff,
  63. TextEditor::textColourId, 0xff000000,
  64. TextEditor::highlightColourId, textHighlightColour,
  65. TextEditor::highlightedTextColourId, 0xff000000,
  66. TextEditor::outlineColourId, 0x00000000,
  67. TextEditor::focusedOutlineColourId, textButtonColour,
  68. TextEditor::shadowColourId, 0x38000000,
  69. CaretComponent::caretColourId, 0xff000000,
  70. Label::backgroundColourId, 0x00000000,
  71. Label::textColourId, 0xff000000,
  72. Label::outlineColourId, 0x00000000,
  73. ScrollBar::backgroundColourId, 0x00000000,
  74. ScrollBar::thumbColourId, 0xffffffff,
  75. TreeView::linesColourId, 0x4c000000,
  76. TreeView::backgroundColourId, 0x00000000,
  77. TreeView::dragAndDropIndicatorColourId, 0x80ff0000,
  78. TreeView::selectedItemBackgroundColourId, 0x00000000,
  79. TreeView::oddItemsColourId, 0x00000000,
  80. TreeView::evenItemsColourId, 0x00000000,
  81. PopupMenu::backgroundColourId, 0xffffffff,
  82. PopupMenu::textColourId, 0xff000000,
  83. PopupMenu::headerTextColourId, 0xff000000,
  84. PopupMenu::highlightedTextColourId, 0xffffffff,
  85. PopupMenu::highlightedBackgroundColourId, 0x991111aa,
  86. ComboBox::buttonColourId, 0xffbbbbff,
  87. ComboBox::outlineColourId, standardOutlineColour,
  88. ComboBox::textColourId, 0xff000000,
  89. ComboBox::backgroundColourId, 0xffffffff,
  90. ComboBox::arrowColourId, 0x99000000,
  91. ComboBox::focusedOutlineColourId, 0xffbbbbff,
  92. PropertyComponent::backgroundColourId, 0x66ffffff,
  93. PropertyComponent::labelTextColourId, 0xff000000,
  94. TextPropertyComponent::backgroundColourId, 0xffffffff,
  95. TextPropertyComponent::textColourId, 0xff000000,
  96. TextPropertyComponent::outlineColourId, standardOutlineColour,
  97. BooleanPropertyComponent::backgroundColourId, 0xffffffff,
  98. BooleanPropertyComponent::outlineColourId, standardOutlineColour,
  99. ListBox::backgroundColourId, 0xffffffff,
  100. ListBox::outlineColourId, standardOutlineColour,
  101. ListBox::textColourId, 0xff000000,
  102. Slider::backgroundColourId, 0x00000000,
  103. Slider::thumbColourId, textButtonColour,
  104. Slider::trackColourId, 0x7fffffff,
  105. Slider::rotarySliderFillColourId, 0x7f0000ff,
  106. Slider::rotarySliderOutlineColourId, 0x66000000,
  107. Slider::textBoxTextColourId, 0xff000000,
  108. Slider::textBoxBackgroundColourId, 0xffffffff,
  109. Slider::textBoxHighlightColourId, textHighlightColour,
  110. Slider::textBoxOutlineColourId, standardOutlineColour,
  111. ResizableWindow::backgroundColourId, 0xff777777,
  112. //DocumentWindow::textColourId, 0xff000000, // (this is deliberately not set)
  113. AlertWindow::backgroundColourId, 0xffededed,
  114. AlertWindow::textColourId, 0xff000000,
  115. AlertWindow::outlineColourId, 0xff666666,
  116. ProgressBar::backgroundColourId, 0xffeeeeee,
  117. ProgressBar::foregroundColourId, 0xffaaaaee,
  118. TooltipWindow::backgroundColourId, 0xffeeeebb,
  119. TooltipWindow::textColourId, 0xff000000,
  120. TooltipWindow::outlineColourId, 0x4c000000,
  121. TabbedComponent::backgroundColourId, 0x00000000,
  122. TabbedComponent::outlineColourId, 0xff777777,
  123. TabbedButtonBar::tabOutlineColourId, 0x80000000,
  124. TabbedButtonBar::frontOutlineColourId, 0x90000000,
  125. Toolbar::backgroundColourId, 0xfff6f8f9,
  126. Toolbar::separatorColourId, 0x4c000000,
  127. Toolbar::buttonMouseOverBackgroundColourId, 0x4c0000ff,
  128. Toolbar::buttonMouseDownBackgroundColourId, 0x800000ff,
  129. Toolbar::labelTextColourId, 0xff000000,
  130. Toolbar::editingModeOutlineColourId, 0xffff0000,
  131. DrawableButton::textColourId, 0xff000000,
  132. DrawableButton::textColourOnId, 0xff000000,
  133. DrawableButton::backgroundColourId, 0x00000000,
  134. DrawableButton::backgroundOnColourId, 0xaabbbbff,
  135. HyperlinkButton::textColourId, 0xcc1111ee,
  136. GroupComponent::outlineColourId, 0x66000000,
  137. GroupComponent::textColourId, 0xff000000,
  138. BubbleComponent::backgroundColourId, 0xeeeeeebb,
  139. BubbleComponent::outlineColourId, 0x77000000,
  140. TableHeaderComponent::textColourId, 0xff000000,
  141. TableHeaderComponent::backgroundColourId, 0xffe8ebf9,
  142. TableHeaderComponent::outlineColourId, 0x33000000,
  143. TableHeaderComponent::highlightColourId, 0x8899aadd,
  144. DirectoryContentsDisplayComponent::highlightColourId, textHighlightColour,
  145. DirectoryContentsDisplayComponent::textColourId, 0xff000000,
  146. DirectoryContentsDisplayComponent::highlightedTextColourId, 0xff000000,
  147. 0x1000440, /*LassoComponent::lassoFillColourId*/ 0x66dddddd,
  148. 0x1000441, /*LassoComponent::lassoOutlineColourId*/ 0x99111111,
  149. 0x1005000, /*MidiKeyboardComponent::whiteNoteColourId*/ 0xffffffff,
  150. 0x1005001, /*MidiKeyboardComponent::blackNoteColourId*/ 0xff000000,
  151. 0x1005002, /*MidiKeyboardComponent::keySeparatorLineColourId*/ 0x66000000,
  152. 0x1005003, /*MidiKeyboardComponent::mouseOverKeyOverlayColourId*/ 0x80ffff00,
  153. 0x1005004, /*MidiKeyboardComponent::keyDownOverlayColourId*/ 0xffb6b600,
  154. 0x1005005, /*MidiKeyboardComponent::textLabelColourId*/ 0xff000000,
  155. 0x1005006, /*MidiKeyboardComponent::upDownButtonBackgroundColourId*/ 0xffd3d3d3,
  156. 0x1005007, /*MidiKeyboardComponent::upDownButtonArrowColourId*/ 0xff000000,
  157. 0x1005008, /*MidiKeyboardComponent::shadowColourId*/ 0x4c000000,
  158. 0x1004500, /*CodeEditorComponent::backgroundColourId*/ 0xffffffff,
  159. 0x1004502, /*CodeEditorComponent::highlightColourId*/ textHighlightColour,
  160. 0x1004503, /*CodeEditorComponent::defaultTextColourId*/ 0xff000000,
  161. 0x1004504, /*CodeEditorComponent::lineNumberBackgroundId*/ 0x44999999,
  162. 0x1004505, /*CodeEditorComponent::lineNumberTextId*/ 0x44000000,
  163. 0x1007000, /*ColourSelector::backgroundColourId*/ 0xffe5e5e5,
  164. 0x1007001, /*ColourSelector::labelTextColourId*/ 0xff000000,
  165. 0x100ad00, /*KeyMappingEditorComponent::backgroundColourId*/ 0x00000000,
  166. 0x100ad01, /*KeyMappingEditorComponent::textColourId*/ 0xff000000,
  167. FileSearchPathListComponent::backgroundColourId, 0xffffffff,
  168. FileChooserDialogBox::titleTextColourId, 0xff000000,
  169. SidePanel::backgroundColour, 0xffffffff,
  170. SidePanel::titleTextColour, 0xff000000,
  171. SidePanel::shadowBaseColour, 0xff000000,
  172. SidePanel::dismissButtonNormalColour, textButtonColour,
  173. SidePanel::dismissButtonOverColour, textButtonColour,
  174. SidePanel::dismissButtonDownColour, 0xff4444ff,
  175. FileBrowserComponent::currentPathBoxBackgroundColourId, 0xffffffff,
  176. FileBrowserComponent::currentPathBoxTextColourId, 0xff000000,
  177. FileBrowserComponent::currentPathBoxArrowColourId, 0x99000000,
  178. FileBrowserComponent::filenameBoxBackgroundColourId, 0xffffffff,
  179. FileBrowserComponent::filenameBoxTextColourId, 0xff000000,
  180. };
  181. for (int i = 0; i < numElementsInArray (standardColours); i += 2)
  182. setColour ((int) standardColours [i], Colour ((uint32) standardColours [i + 1]));
  183. }
  184. LookAndFeel_V2::~LookAndFeel_V2() {}
  185. //==============================================================================
  186. void LookAndFeel_V2::drawButtonBackground (Graphics& g,
  187. Button& button,
  188. const Colour& backgroundColour,
  189. bool isMouseOverButton,
  190. bool isButtonDown)
  191. {
  192. const int width = button.getWidth();
  193. const int height = button.getHeight();
  194. const float outlineThickness = button.isEnabled() ? ((isButtonDown || isMouseOverButton) ? 1.2f : 0.7f) : 0.4f;
  195. const float halfThickness = outlineThickness * 0.5f;
  196. const float indentL = button.isConnectedOnLeft() ? 0.1f : halfThickness;
  197. const float indentR = button.isConnectedOnRight() ? 0.1f : halfThickness;
  198. const float indentT = button.isConnectedOnTop() ? 0.1f : halfThickness;
  199. const float indentB = button.isConnectedOnBottom() ? 0.1f : halfThickness;
  200. const Colour baseColour (LookAndFeelHelpers::createBaseColour (backgroundColour,
  201. button.hasKeyboardFocus (true),
  202. isMouseOverButton, isButtonDown)
  203. .withMultipliedAlpha (button.isEnabled() ? 1.0f : 0.5f));
  204. drawGlassLozenge (g,
  205. indentL,
  206. indentT,
  207. width - indentL - indentR,
  208. height - indentT - indentB,
  209. baseColour, outlineThickness, -1.0f,
  210. button.isConnectedOnLeft(),
  211. button.isConnectedOnRight(),
  212. button.isConnectedOnTop(),
  213. button.isConnectedOnBottom());
  214. }
  215. Font LookAndFeel_V2::getTextButtonFont (TextButton&, int buttonHeight)
  216. {
  217. return Font (jmin (15.0f, buttonHeight * 0.6f));
  218. }
  219. int LookAndFeel_V2::getTextButtonWidthToFitText (TextButton& b, int buttonHeight)
  220. {
  221. return getTextButtonFont (b, buttonHeight).getStringWidth (b.getButtonText()) + buttonHeight;
  222. }
  223. void LookAndFeel_V2::drawButtonText (Graphics& g, TextButton& button, bool /*isMouseOverButton*/, bool /*isButtonDown*/)
  224. {
  225. Font font (getTextButtonFont (button, button.getHeight()));
  226. g.setFont (font);
  227. g.setColour (button.findColour (button.getToggleState() ? TextButton::textColourOnId
  228. : TextButton::textColourOffId)
  229. .withMultipliedAlpha (button.isEnabled() ? 1.0f : 0.5f));
  230. const int yIndent = jmin (4, button.proportionOfHeight (0.3f));
  231. const int cornerSize = jmin (button.getHeight(), button.getWidth()) / 2;
  232. const int fontHeight = roundToInt (font.getHeight() * 0.6f);
  233. const int leftIndent = jmin (fontHeight, 2 + cornerSize / (button.isConnectedOnLeft() ? 4 : 2));
  234. const int rightIndent = jmin (fontHeight, 2 + cornerSize / (button.isConnectedOnRight() ? 4 : 2));
  235. const int textWidth = button.getWidth() - leftIndent - rightIndent;
  236. if (textWidth > 0)
  237. g.drawFittedText (button.getButtonText(),
  238. leftIndent, yIndent, textWidth, button.getHeight() - yIndent * 2,
  239. Justification::centred, 2);
  240. }
  241. void LookAndFeel_V2::drawTickBox (Graphics& g, Component& component,
  242. float x, float y, float w, float h,
  243. const bool ticked,
  244. const bool isEnabled,
  245. const bool isMouseOverButton,
  246. const bool isButtonDown)
  247. {
  248. const float boxSize = w * 0.7f;
  249. drawGlassSphere (g, x, y + (h - boxSize) * 0.5f, boxSize,
  250. LookAndFeelHelpers::createBaseColour (component.findColour (TextButton::buttonColourId)
  251. .withMultipliedAlpha (isEnabled ? 1.0f : 0.5f),
  252. true, isMouseOverButton, isButtonDown),
  253. isEnabled ? ((isButtonDown || isMouseOverButton) ? 1.1f : 0.5f) : 0.3f);
  254. if (ticked)
  255. {
  256. Path tick;
  257. tick.startNewSubPath (1.5f, 3.0f);
  258. tick.lineTo (3.0f, 6.0f);
  259. tick.lineTo (6.0f, 0.0f);
  260. g.setColour (component.findColour (isEnabled ? ToggleButton::tickColourId
  261. : ToggleButton::tickDisabledColourId));
  262. const AffineTransform trans (AffineTransform::scale (w / 9.0f, h / 9.0f)
  263. .translated (x, y));
  264. g.strokePath (tick, PathStrokeType (2.5f), trans);
  265. }
  266. }
  267. void LookAndFeel_V2::drawToggleButton (Graphics& g, ToggleButton& button,
  268. bool isMouseOverButton, bool isButtonDown)
  269. {
  270. if (button.hasKeyboardFocus (true))
  271. {
  272. g.setColour (button.findColour (TextEditor::focusedOutlineColourId));
  273. g.drawRect (0, 0, button.getWidth(), button.getHeight());
  274. }
  275. float fontSize = jmin (15.0f, button.getHeight() * 0.75f);
  276. const float tickWidth = fontSize * 1.1f;
  277. drawTickBox (g, button, 4.0f, (button.getHeight() - tickWidth) * 0.5f,
  278. tickWidth, tickWidth,
  279. button.getToggleState(),
  280. button.isEnabled(),
  281. isMouseOverButton,
  282. isButtonDown);
  283. g.setColour (button.findColour (ToggleButton::textColourId));
  284. g.setFont (fontSize);
  285. if (! button.isEnabled())
  286. g.setOpacity (0.5f);
  287. g.drawFittedText (button.getButtonText(),
  288. button.getLocalBounds().withTrimmedLeft (roundToInt (tickWidth) + 5)
  289. .withTrimmedRight (2),
  290. Justification::centredLeft, 10);
  291. }
  292. void LookAndFeel_V2::changeToggleButtonWidthToFitText (ToggleButton& button)
  293. {
  294. auto fontSize = jmin (15.0f, button.getHeight() * 0.75f);
  295. auto tickWidth = fontSize * 1.1f;
  296. Font font (fontSize);
  297. button.setSize (font.getStringWidth (button.getButtonText()) + roundToInt (tickWidth) + 9,
  298. button.getHeight());
  299. }
  300. void LookAndFeel_V2::drawDrawableButton (Graphics& g, DrawableButton& button,
  301. bool /*isMouseOverButton*/, bool /*isButtonDown*/)
  302. {
  303. bool toggleState = button.getToggleState();
  304. g.fillAll (button.findColour (toggleState ? DrawableButton::backgroundOnColourId
  305. : DrawableButton::backgroundColourId));
  306. const int textH = (button.getStyle() == DrawableButton::ImageAboveTextLabel)
  307. ? jmin (16, button.proportionOfHeight (0.25f))
  308. : 0;
  309. if (textH > 0)
  310. {
  311. g.setFont ((float) textH);
  312. g.setColour (button.findColour (toggleState ? DrawableButton::textColourOnId
  313. : DrawableButton::textColourId)
  314. .withMultipliedAlpha (button.isEnabled() ? 1.0f : 0.4f));
  315. g.drawFittedText (button.getButtonText(),
  316. 2, button.getHeight() - textH - 1,
  317. button.getWidth() - 4, textH,
  318. Justification::centred, 1);
  319. }
  320. }
  321. //==============================================================================
  322. AlertWindow* LookAndFeel_V2::createAlertWindow (const String& title, const String& message,
  323. const String& button1, const String& button2, const String& button3,
  324. AlertWindow::AlertIconType iconType,
  325. int numButtons, Component* associatedComponent)
  326. {
  327. AlertWindow* aw = new AlertWindow (title, message, iconType, associatedComponent);
  328. if (numButtons == 1)
  329. {
  330. aw->addButton (button1, 0,
  331. KeyPress (KeyPress::escapeKey),
  332. KeyPress (KeyPress::returnKey));
  333. }
  334. else
  335. {
  336. const KeyPress button1ShortCut ((int) CharacterFunctions::toLowerCase (button1[0]), 0, 0);
  337. KeyPress button2ShortCut ((int) CharacterFunctions::toLowerCase (button2[0]), 0, 0);
  338. if (button1ShortCut == button2ShortCut)
  339. button2ShortCut = KeyPress();
  340. if (numButtons == 2)
  341. {
  342. aw->addButton (button1, 1, KeyPress (KeyPress::returnKey), button1ShortCut);
  343. aw->addButton (button2, 0, KeyPress (KeyPress::escapeKey), button2ShortCut);
  344. }
  345. else if (numButtons == 3)
  346. {
  347. aw->addButton (button1, 1, button1ShortCut);
  348. aw->addButton (button2, 2, button2ShortCut);
  349. aw->addButton (button3, 0, KeyPress (KeyPress::escapeKey));
  350. }
  351. }
  352. return aw;
  353. }
  354. void LookAndFeel_V2::drawAlertBox (Graphics& g, AlertWindow& alert,
  355. const Rectangle<int>& textArea, TextLayout& textLayout)
  356. {
  357. g.fillAll (alert.findColour (AlertWindow::backgroundColourId));
  358. int iconSpaceUsed = 0;
  359. const int iconWidth = 80;
  360. int iconSize = jmin (iconWidth + 50, alert.getHeight() + 20);
  361. if (alert.containsAnyExtraComponents() || alert.getNumButtons() > 2)
  362. iconSize = jmin (iconSize, textArea.getHeight() + 50);
  363. const Rectangle<int> iconRect (iconSize / -10, iconSize / -10,
  364. iconSize, iconSize);
  365. if (alert.getAlertType() != AlertWindow::NoIcon)
  366. {
  367. Path icon;
  368. uint32 colour;
  369. char character;
  370. if (alert.getAlertType() == AlertWindow::WarningIcon)
  371. {
  372. colour = 0x55ff5555;
  373. character = '!';
  374. icon.addTriangle (iconRect.getX() + iconRect.getWidth() * 0.5f, (float) iconRect.getY(),
  375. (float) iconRect.getRight(), (float) iconRect.getBottom(),
  376. (float) iconRect.getX(), (float) iconRect.getBottom());
  377. icon = icon.createPathWithRoundedCorners (5.0f);
  378. }
  379. else
  380. {
  381. colour = alert.getAlertType() == AlertWindow::InfoIcon ? (uint32) 0x605555ff : (uint32) 0x40b69900;
  382. character = alert.getAlertType() == AlertWindow::InfoIcon ? 'i' : '?';
  383. icon.addEllipse (iconRect.toFloat());
  384. }
  385. GlyphArrangement ga;
  386. ga.addFittedText (Font (iconRect.getHeight() * 0.9f, Font::bold),
  387. String::charToString ((juce_wchar) (uint8) character),
  388. (float) iconRect.getX(), (float) iconRect.getY(),
  389. (float) iconRect.getWidth(), (float) iconRect.getHeight(),
  390. Justification::centred, false);
  391. ga.createPath (icon);
  392. icon.setUsingNonZeroWinding (false);
  393. g.setColour (Colour (colour));
  394. g.fillPath (icon);
  395. iconSpaceUsed = iconWidth;
  396. }
  397. g.setColour (alert.findColour (AlertWindow::textColourId));
  398. textLayout.draw (g, Rectangle<int> (textArea.getX() + iconSpaceUsed,
  399. textArea.getY(),
  400. textArea.getWidth() - iconSpaceUsed,
  401. textArea.getHeight()).toFloat());
  402. g.setColour (alert.findColour (AlertWindow::outlineColourId));
  403. g.drawRect (0, 0, alert.getWidth(), alert.getHeight());
  404. }
  405. int LookAndFeel_V2::getAlertBoxWindowFlags()
  406. {
  407. return ComponentPeer::windowAppearsOnTaskbar
  408. | ComponentPeer::windowHasDropShadow;
  409. }
  410. Array<int> LookAndFeel_V2::getWidthsForTextButtons (AlertWindow&, const Array<TextButton*>& buttons)
  411. {
  412. const int n = buttons.size();
  413. Array<int> buttonWidths;
  414. const int buttonHeight = getAlertWindowButtonHeight();
  415. for (int i = 0; i < n; ++i)
  416. buttonWidths.add (getTextButtonWidthToFitText (*buttons.getReference (i), buttonHeight));
  417. return buttonWidths;
  418. }
  419. int LookAndFeel_V2::getAlertWindowButtonHeight()
  420. {
  421. return 28;
  422. }
  423. Font LookAndFeel_V2::getAlertWindowTitleFont()
  424. {
  425. Font messageFont = getAlertWindowMessageFont();
  426. return messageFont.withHeight (messageFont.getHeight() * 1.1f).boldened();
  427. }
  428. Font LookAndFeel_V2::getAlertWindowMessageFont()
  429. {
  430. return Font (15.0f);
  431. }
  432. Font LookAndFeel_V2::getAlertWindowFont()
  433. {
  434. return Font (12.0f);
  435. }
  436. //==============================================================================
  437. void LookAndFeel_V2::drawProgressBar (Graphics& g, ProgressBar& progressBar,
  438. int width, int height,
  439. double progress, const String& textToShow)
  440. {
  441. const Colour background (progressBar.findColour (ProgressBar::backgroundColourId));
  442. const Colour foreground (progressBar.findColour (ProgressBar::foregroundColourId));
  443. g.fillAll (background);
  444. if (progress >= 0.0f && progress < 1.0f)
  445. {
  446. drawGlassLozenge (g, 1.0f, 1.0f,
  447. (float) jlimit (0.0, width - 2.0, progress * (width - 2.0)),
  448. (float) (height - 2),
  449. foreground,
  450. 0.5f, 0.0f,
  451. true, true, true, true);
  452. }
  453. else
  454. {
  455. // spinning bar..
  456. g.setColour (foreground);
  457. const int stripeWidth = height * 2;
  458. const int position = (int) (Time::getMillisecondCounter() / 15) % stripeWidth;
  459. Path p;
  460. for (float x = (float) (- position); x < width + stripeWidth; x += stripeWidth)
  461. p.addQuadrilateral (x, 0.0f,
  462. x + stripeWidth * 0.5f, 0.0f,
  463. x, (float) height,
  464. x - stripeWidth * 0.5f, (float) height);
  465. Image im (Image::ARGB, width, height, true);
  466. {
  467. Graphics g2 (im);
  468. drawGlassLozenge (g2, 1.0f, 1.0f,
  469. (float) (width - 2),
  470. (float) (height - 2),
  471. foreground,
  472. 0.5f, 0.0f,
  473. true, true, true, true);
  474. }
  475. g.setTiledImageFill (im, 0, 0, 0.85f);
  476. g.fillPath (p);
  477. }
  478. if (textToShow.isNotEmpty())
  479. {
  480. g.setColour (Colour::contrasting (background, foreground));
  481. g.setFont (height * 0.6f);
  482. g.drawText (textToShow, 0, 0, width, height, Justification::centred, false);
  483. }
  484. }
  485. void LookAndFeel_V2::drawSpinningWaitAnimation (Graphics& g, const Colour& colour, int x, int y, int w, int h)
  486. {
  487. const float radius = jmin (w, h) * 0.4f;
  488. const float thickness = radius * 0.15f;
  489. Path p;
  490. p.addRoundedRectangle (radius * 0.4f, thickness * -0.5f,
  491. radius * 0.6f, thickness,
  492. thickness * 0.5f);
  493. const float cx = x + w * 0.5f;
  494. const float cy = y + h * 0.5f;
  495. const uint32 animationIndex = (Time::getMillisecondCounter() / (1000 / 10)) % 12;
  496. for (uint32 i = 0; i < 12; ++i)
  497. {
  498. const uint32 n = (i + 12 - animationIndex) % 12;
  499. g.setColour (colour.withMultipliedAlpha ((n + 1) / 12.0f));
  500. g.fillPath (p, AffineTransform::rotation (i * (MathConstants<float>::pi / 6.0f))
  501. .translated (cx, cy));
  502. }
  503. }
  504. bool LookAndFeel_V2::isProgressBarOpaque (ProgressBar& progressBar)
  505. {
  506. return progressBar.findColour (ProgressBar::backgroundColourId).isOpaque();
  507. }
  508. bool LookAndFeel_V2::areScrollbarButtonsVisible()
  509. {
  510. return true;
  511. }
  512. void LookAndFeel_V2::drawScrollbarButton (Graphics& g, ScrollBar& scrollbar,
  513. int width, int height, int buttonDirection,
  514. bool /*isScrollbarVertical*/,
  515. bool /*isMouseOverButton*/,
  516. bool isButtonDown)
  517. {
  518. Path p;
  519. if (buttonDirection == 0)
  520. p.addTriangle (width * 0.5f, height * 0.2f,
  521. width * 0.1f, height * 0.7f,
  522. width * 0.9f, height * 0.7f);
  523. else if (buttonDirection == 1)
  524. p.addTriangle (width * 0.8f, height * 0.5f,
  525. width * 0.3f, height * 0.1f,
  526. width * 0.3f, height * 0.9f);
  527. else if (buttonDirection == 2)
  528. p.addTriangle (width * 0.5f, height * 0.8f,
  529. width * 0.1f, height * 0.3f,
  530. width * 0.9f, height * 0.3f);
  531. else if (buttonDirection == 3)
  532. p.addTriangle (width * 0.2f, height * 0.5f,
  533. width * 0.7f, height * 0.1f,
  534. width * 0.7f, height * 0.9f);
  535. if (isButtonDown)
  536. g.setColour (scrollbar.findColour (ScrollBar::thumbColourId).contrasting (0.2f));
  537. else
  538. g.setColour (scrollbar.findColour (ScrollBar::thumbColourId));
  539. g.fillPath (p);
  540. g.setColour (Colour (0x80000000));
  541. g.strokePath (p, PathStrokeType (0.5f));
  542. }
  543. void LookAndFeel_V2::drawScrollbar (Graphics& g,
  544. ScrollBar& scrollbar,
  545. int x, int y,
  546. int width, int height,
  547. bool isScrollbarVertical,
  548. int thumbStartPosition,
  549. int thumbSize,
  550. bool /*isMouseOver*/,
  551. bool /*isMouseDown*/)
  552. {
  553. g.fillAll (scrollbar.findColour (ScrollBar::backgroundColourId));
  554. Path slotPath, thumbPath;
  555. const float slotIndent = jmin (width, height) > 15 ? 1.0f : 0.0f;
  556. const float slotIndentx2 = slotIndent * 2.0f;
  557. const float thumbIndent = slotIndent + 1.0f;
  558. const float thumbIndentx2 = thumbIndent * 2.0f;
  559. float gx1 = 0.0f, gy1 = 0.0f, gx2 = 0.0f, gy2 = 0.0f;
  560. if (isScrollbarVertical)
  561. {
  562. slotPath.addRoundedRectangle (x + slotIndent,
  563. y + slotIndent,
  564. width - slotIndentx2,
  565. height - slotIndentx2,
  566. (width - slotIndentx2) * 0.5f);
  567. if (thumbSize > 0)
  568. thumbPath.addRoundedRectangle (x + thumbIndent,
  569. thumbStartPosition + thumbIndent,
  570. width - thumbIndentx2,
  571. thumbSize - thumbIndentx2,
  572. (width - thumbIndentx2) * 0.5f);
  573. gx1 = (float) x;
  574. gx2 = x + width * 0.7f;
  575. }
  576. else
  577. {
  578. slotPath.addRoundedRectangle (x + slotIndent,
  579. y + slotIndent,
  580. width - slotIndentx2,
  581. height - slotIndentx2,
  582. (height - slotIndentx2) * 0.5f);
  583. if (thumbSize > 0)
  584. thumbPath.addRoundedRectangle (thumbStartPosition + thumbIndent,
  585. y + thumbIndent,
  586. thumbSize - thumbIndentx2,
  587. height - thumbIndentx2,
  588. (height - thumbIndentx2) * 0.5f);
  589. gy1 = (float) y;
  590. gy2 = y + height * 0.7f;
  591. }
  592. const Colour thumbColour (scrollbar.findColour (ScrollBar::thumbColourId));
  593. Colour trackColour1, trackColour2;
  594. if (scrollbar.isColourSpecified (ScrollBar::trackColourId)
  595. || isColourSpecified (ScrollBar::trackColourId))
  596. {
  597. trackColour1 = trackColour2 = scrollbar.findColour (ScrollBar::trackColourId);
  598. }
  599. else
  600. {
  601. trackColour1 = thumbColour.overlaidWith (Colour (0x44000000));
  602. trackColour2 = thumbColour.overlaidWith (Colour (0x19000000));
  603. }
  604. g.setGradientFill (ColourGradient (trackColour1, gx1, gy1,
  605. trackColour2, gx2, gy2, false));
  606. g.fillPath (slotPath);
  607. if (isScrollbarVertical)
  608. {
  609. gx1 = x + width * 0.6f;
  610. gx2 = (float) x + width;
  611. }
  612. else
  613. {
  614. gy1 = y + height * 0.6f;
  615. gy2 = (float) y + height;
  616. }
  617. g.setGradientFill (ColourGradient (Colours::transparentBlack,gx1, gy1,
  618. Colour (0x19000000), gx2, gy2, false));
  619. g.fillPath (slotPath);
  620. g.setColour (thumbColour);
  621. g.fillPath (thumbPath);
  622. g.setGradientFill (ColourGradient (Colour (0x10000000), gx1, gy1,
  623. Colours::transparentBlack, gx2, gy2, false));
  624. g.saveState();
  625. if (isScrollbarVertical)
  626. g.reduceClipRegion (x + width / 2, y, width, height);
  627. else
  628. g.reduceClipRegion (x, y + height / 2, width, height);
  629. g.fillPath (thumbPath);
  630. g.restoreState();
  631. g.setColour (Colour (0x4c000000));
  632. g.strokePath (thumbPath, PathStrokeType (0.4f));
  633. }
  634. ImageEffectFilter* LookAndFeel_V2::getScrollbarEffect()
  635. {
  636. return nullptr;
  637. }
  638. int LookAndFeel_V2::getMinimumScrollbarThumbSize (ScrollBar& scrollbar)
  639. {
  640. return jmin (scrollbar.getWidth(), scrollbar.getHeight()) * 2;
  641. }
  642. int LookAndFeel_V2::getDefaultScrollbarWidth()
  643. {
  644. return 18;
  645. }
  646. int LookAndFeel_V2::getScrollbarButtonSize (ScrollBar& scrollbar)
  647. {
  648. return 2 + (scrollbar.isVertical() ? scrollbar.getWidth()
  649. : scrollbar.getHeight());
  650. }
  651. //==============================================================================
  652. void LookAndFeel_V2::drawTreeviewPlusMinusBox (Graphics& g, const Rectangle<float>& area,
  653. Colour /*backgroundColour*/, bool isOpen, bool /*isMouseOver*/)
  654. {
  655. auto boxSize = roundToInt (jmin (16.0f, area.getWidth(), area.getHeight()) * 0.7f) | 1;
  656. auto x = ((int) area.getWidth() - boxSize) / 2 + (int) area.getX();
  657. auto y = ((int) area.getHeight() - boxSize) / 2 + (int) area.getY();
  658. Rectangle<float> boxArea ((float) x, (float) y, (float) boxSize, (float) boxSize);
  659. g.setColour (Colour (0xe5ffffff));
  660. g.fillRect (boxArea);
  661. g.setColour (Colour (0x80000000));
  662. g.drawRect (boxArea);
  663. auto size = boxSize / 2 + 1.0f;
  664. auto centre = (float) (boxSize / 2);
  665. g.fillRect (x + (boxSize - size) * 0.5f, y + centre, size, 1.0f);
  666. if (! isOpen)
  667. g.fillRect (x + centre, y + (boxSize - size) * 0.5f, 1.0f, size);
  668. }
  669. bool LookAndFeel_V2::areLinesDrawnForTreeView (TreeView&)
  670. {
  671. return true;
  672. }
  673. int LookAndFeel_V2::getTreeViewIndentSize (TreeView&)
  674. {
  675. return 24;
  676. }
  677. //==============================================================================
  678. void LookAndFeel_V2::drawBubble (Graphics& g, BubbleComponent& comp,
  679. const Point<float>& tip, const Rectangle<float>& body)
  680. {
  681. Path p;
  682. p.addBubble (body.reduced (0.5f), body.getUnion (Rectangle<float> (tip.x, tip.y, 1.0f, 1.0f)),
  683. tip, 5.0f, jmin (15.0f, body.getWidth() * 0.2f, body.getHeight() * 0.2f));
  684. g.setColour (comp.findColour (BubbleComponent::backgroundColourId));
  685. g.fillPath (p);
  686. g.setColour (comp.findColour (BubbleComponent::outlineColourId));
  687. g.strokePath (p, PathStrokeType (1.0f));
  688. }
  689. //==============================================================================
  690. Font LookAndFeel_V2::getPopupMenuFont()
  691. {
  692. return Font (17.0f);
  693. }
  694. void LookAndFeel_V2::getIdealPopupMenuItemSize (const String& text, const bool isSeparator,
  695. int standardMenuItemHeight, int& idealWidth, int& idealHeight)
  696. {
  697. if (isSeparator)
  698. {
  699. idealWidth = 50;
  700. idealHeight = standardMenuItemHeight > 0 ? standardMenuItemHeight / 2 : 10;
  701. }
  702. else
  703. {
  704. Font font (getPopupMenuFont());
  705. if (standardMenuItemHeight > 0 && font.getHeight() > standardMenuItemHeight / 1.3f)
  706. font.setHeight (standardMenuItemHeight / 1.3f);
  707. idealHeight = standardMenuItemHeight > 0 ? standardMenuItemHeight : roundToInt (font.getHeight() * 1.3f);
  708. idealWidth = font.getStringWidth (text) + idealHeight * 2;
  709. }
  710. }
  711. void LookAndFeel_V2::drawPopupMenuBackground (Graphics& g, int width, int height)
  712. {
  713. auto background = findColour (PopupMenu::backgroundColourId);
  714. g.fillAll (background);
  715. g.setColour (background.overlaidWith (Colour (0x2badd8e6)));
  716. for (int i = 0; i < height; i += 3)
  717. g.fillRect (0, i, width, 1);
  718. #if ! JUCE_MAC
  719. g.setColour (findColour (PopupMenu::textColourId).withAlpha (0.6f));
  720. g.drawRect (0, 0, width, height);
  721. #endif
  722. }
  723. void LookAndFeel_V2::drawPopupMenuUpDownArrow (Graphics& g, int width, int height, bool isScrollUpArrow)
  724. {
  725. auto background = findColour (PopupMenu::backgroundColourId);
  726. g.setGradientFill (ColourGradient (background, 0.0f, height * 0.5f,
  727. background.withAlpha (0.0f),
  728. 0.0f, isScrollUpArrow ? ((float) height) : 0.0f,
  729. false));
  730. g.fillRect (1, 1, width - 2, height - 2);
  731. auto hw = width * 0.5f;
  732. auto arrowW = height * 0.3f;
  733. auto y1 = height * (isScrollUpArrow ? 0.6f : 0.3f);
  734. auto y2 = height * (isScrollUpArrow ? 0.3f : 0.6f);
  735. Path p;
  736. p.addTriangle (hw - arrowW, y1,
  737. hw + arrowW, y1,
  738. hw, y2);
  739. g.setColour (findColour (PopupMenu::textColourId).withAlpha (0.5f));
  740. g.fillPath (p);
  741. }
  742. void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, const Rectangle<int>& area,
  743. const bool isSeparator, const bool isActive,
  744. const bool isHighlighted, const bool isTicked,
  745. const bool hasSubMenu, const String& text,
  746. const String& shortcutKeyText,
  747. const Drawable* icon, const Colour* const textColourToUse)
  748. {
  749. if (isSeparator)
  750. {
  751. auto r = area.reduced (5, 0);
  752. r.removeFromTop (r.getHeight() / 2 - 1);
  753. g.setColour (Colour (0x33000000));
  754. g.fillRect (r.removeFromTop (1));
  755. g.setColour (Colour (0x66ffffff));
  756. g.fillRect (r.removeFromTop (1));
  757. }
  758. else
  759. {
  760. auto textColour = findColour (PopupMenu::textColourId);
  761. if (textColourToUse != nullptr)
  762. textColour = *textColourToUse;
  763. auto r = area.reduced (1);
  764. if (isHighlighted)
  765. {
  766. g.setColour (findColour (PopupMenu::highlightedBackgroundColourId));
  767. g.fillRect (r);
  768. g.setColour (findColour (PopupMenu::highlightedTextColourId));
  769. }
  770. else
  771. {
  772. g.setColour (textColour);
  773. }
  774. if (! isActive)
  775. g.setOpacity (0.3f);
  776. Font font (getPopupMenuFont());
  777. auto maxFontHeight = area.getHeight() / 1.3f;
  778. if (font.getHeight() > maxFontHeight)
  779. font.setHeight (maxFontHeight);
  780. g.setFont (font);
  781. auto iconArea = r.removeFromLeft ((r.getHeight() * 5) / 4).reduced (3).toFloat();
  782. if (icon != nullptr)
  783. {
  784. icon->drawWithin (g, iconArea, RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, 1.0f);
  785. }
  786. else if (isTicked)
  787. {
  788. auto tick = getTickShape (1.0f);
  789. g.fillPath (tick, tick.getTransformToScaleToFit (iconArea, true));
  790. }
  791. if (hasSubMenu)
  792. {
  793. auto arrowH = 0.6f * getPopupMenuFont().getAscent();
  794. auto x = (float) r.removeFromRight ((int) arrowH).getX();
  795. auto halfH = (float) r.getCentreY();
  796. Path p;
  797. p.addTriangle (x, halfH - arrowH * 0.5f,
  798. x, halfH + arrowH * 0.5f,
  799. x + arrowH * 0.6f, halfH);
  800. g.fillPath (p);
  801. }
  802. r.removeFromRight (3);
  803. g.drawFittedText (text, r, Justification::centredLeft, 1);
  804. if (shortcutKeyText.isNotEmpty())
  805. {
  806. Font f2 (font);
  807. f2.setHeight (f2.getHeight() * 0.75f);
  808. f2.setHorizontalScale (0.95f);
  809. g.setFont (f2);
  810. g.drawText (shortcutKeyText, r, Justification::centredRight, true);
  811. }
  812. }
  813. }
  814. void LookAndFeel_V2::drawPopupMenuSectionHeader (Graphics& g, const Rectangle<int>& area, const String& sectionName)
  815. {
  816. g.setFont (getPopupMenuFont().boldened());
  817. g.setColour (findColour (PopupMenu::headerTextColourId));
  818. g.drawFittedText (sectionName,
  819. area.getX() + 12, area.getY(), area.getWidth() - 16, (int) (area.getHeight() * 0.8f),
  820. Justification::bottomLeft, 1);
  821. }
  822. //==============================================================================
  823. int LookAndFeel_V2::getMenuWindowFlags()
  824. {
  825. return ComponentPeer::windowHasDropShadow;
  826. }
  827. void LookAndFeel_V2::drawMenuBarBackground (Graphics& g, int width, int height, bool, MenuBarComponent& menuBar)
  828. {
  829. auto baseColour = LookAndFeelHelpers::createBaseColour (menuBar.findColour (PopupMenu::backgroundColourId),
  830. false, false, false);
  831. if (menuBar.isEnabled())
  832. drawShinyButtonShape (g, -4.0f, 0.0f, width + 8.0f, (float) height,
  833. 0.0f, baseColour, 0.4f, true, true, true, true);
  834. else
  835. g.fillAll (baseColour);
  836. }
  837. Font LookAndFeel_V2::getMenuBarFont (MenuBarComponent& menuBar, int /*itemIndex*/, const String& /*itemText*/)
  838. {
  839. return Font (menuBar.getHeight() * 0.7f);
  840. }
  841. int LookAndFeel_V2::getMenuBarItemWidth (MenuBarComponent& menuBar, int itemIndex, const String& itemText)
  842. {
  843. return getMenuBarFont (menuBar, itemIndex, itemText)
  844. .getStringWidth (itemText) + menuBar.getHeight();
  845. }
  846. void LookAndFeel_V2::drawMenuBarItem (Graphics& g, int width, int height,
  847. int itemIndex, const String& itemText,
  848. bool isMouseOverItem, bool isMenuOpen,
  849. bool /*isMouseOverBar*/, MenuBarComponent& menuBar)
  850. {
  851. if (! menuBar.isEnabled())
  852. {
  853. g.setColour (menuBar.findColour (PopupMenu::textColourId)
  854. .withMultipliedAlpha (0.5f));
  855. }
  856. else if (isMenuOpen || isMouseOverItem)
  857. {
  858. g.fillAll (menuBar.findColour (PopupMenu::highlightedBackgroundColourId));
  859. g.setColour (menuBar.findColour (PopupMenu::highlightedTextColourId));
  860. }
  861. else
  862. {
  863. g.setColour (menuBar.findColour (PopupMenu::textColourId));
  864. }
  865. g.setFont (getMenuBarFont (menuBar, itemIndex, itemText));
  866. g.drawFittedText (itemText, 0, 0, width, height, Justification::centred, 1);
  867. }
  868. Component* LookAndFeel_V2::getParentComponentForMenuOptions (const PopupMenu::Options& options)
  869. {
  870. return options.getParentComponent();
  871. }
  872. void LookAndFeel_V2::preparePopupMenuWindow (Component&) {}
  873. bool LookAndFeel_V2::shouldPopupMenuScaleWithTargetComponent (const PopupMenu::Options&) { return true; }
  874. int LookAndFeel_V2::getPopupMenuBorderSize() { return 2; }
  875. //==============================================================================
  876. void LookAndFeel_V2::fillTextEditorBackground (Graphics& g, int /*width*/, int /*height*/, TextEditor& textEditor)
  877. {
  878. g.fillAll (textEditor.findColour (TextEditor::backgroundColourId));
  879. }
  880. void LookAndFeel_V2::drawTextEditorOutline (Graphics& g, int width, int height, TextEditor& textEditor)
  881. {
  882. if (textEditor.isEnabled())
  883. {
  884. if (textEditor.hasKeyboardFocus (true) && ! textEditor.isReadOnly())
  885. {
  886. const int border = 2;
  887. g.setColour (textEditor.findColour (TextEditor::focusedOutlineColourId));
  888. g.drawRect (0, 0, width, height, border);
  889. g.setOpacity (1.0f);
  890. auto shadowColour = textEditor.findColour (TextEditor::shadowColourId).withMultipliedAlpha (0.75f);
  891. drawBevel (g, 0, 0, width, height + 2, border + 2, shadowColour, shadowColour);
  892. }
  893. else
  894. {
  895. g.setColour (textEditor.findColour (TextEditor::outlineColourId));
  896. g.drawRect (0, 0, width, height);
  897. g.setOpacity (1.0f);
  898. auto shadowColour = textEditor.findColour (TextEditor::shadowColourId);
  899. drawBevel (g, 0, 0, width, height + 2, 3, shadowColour, shadowColour);
  900. }
  901. }
  902. }
  903. CaretComponent* LookAndFeel_V2::createCaretComponent (Component* keyFocusOwner)
  904. {
  905. return new CaretComponent (keyFocusOwner);
  906. }
  907. //==============================================================================
  908. void LookAndFeel_V2::drawComboBox (Graphics& g, int width, int height, const bool isButtonDown,
  909. int buttonX, int buttonY, int buttonW, int buttonH, ComboBox& box)
  910. {
  911. g.fillAll (box.findColour (ComboBox::backgroundColourId));
  912. if (box.isEnabled() && box.hasKeyboardFocus (false))
  913. {
  914. g.setColour (box.findColour (ComboBox::focusedOutlineColourId));
  915. g.drawRect (0, 0, width, height, 2);
  916. }
  917. else
  918. {
  919. g.setColour (box.findColour (ComboBox::outlineColourId));
  920. g.drawRect (0, 0, width, height);
  921. }
  922. auto outlineThickness = box.isEnabled() ? (isButtonDown ? 1.2f : 0.5f) : 0.3f;
  923. auto baseColour = LookAndFeelHelpers::createBaseColour (box.findColour (ComboBox::buttonColourId),
  924. box.hasKeyboardFocus (true),
  925. false, isButtonDown)
  926. .withMultipliedAlpha (box.isEnabled() ? 1.0f : 0.5f);
  927. drawGlassLozenge (g,
  928. buttonX + outlineThickness, buttonY + outlineThickness,
  929. buttonW - outlineThickness * 2.0f, buttonH - outlineThickness * 2.0f,
  930. baseColour, outlineThickness, -1.0f,
  931. true, true, true, true);
  932. if (box.isEnabled())
  933. {
  934. const float arrowX = 0.3f;
  935. const float arrowH = 0.2f;
  936. Path p;
  937. p.addTriangle (buttonX + buttonW * 0.5f, buttonY + buttonH * (0.45f - arrowH),
  938. buttonX + buttonW * (1.0f - arrowX), buttonY + buttonH * 0.45f,
  939. buttonX + buttonW * arrowX, buttonY + buttonH * 0.45f);
  940. p.addTriangle (buttonX + buttonW * 0.5f, buttonY + buttonH * (0.55f + arrowH),
  941. buttonX + buttonW * (1.0f - arrowX), buttonY + buttonH * 0.55f,
  942. buttonX + buttonW * arrowX, buttonY + buttonH * 0.55f);
  943. g.setColour (box.findColour (ComboBox::arrowColourId));
  944. g.fillPath (p);
  945. }
  946. }
  947. Font LookAndFeel_V2::getComboBoxFont (ComboBox& box)
  948. {
  949. return Font (jmin (15.0f, box.getHeight() * 0.85f));
  950. }
  951. Label* LookAndFeel_V2::createComboBoxTextBox (ComboBox&)
  952. {
  953. return new Label (String(), String());
  954. }
  955. void LookAndFeel_V2::positionComboBoxText (ComboBox& box, Label& label)
  956. {
  957. label.setBounds (1, 1,
  958. box.getWidth() + 3 - box.getHeight(),
  959. box.getHeight() - 2);
  960. label.setFont (getComboBoxFont (box));
  961. }
  962. PopupMenu::Options LookAndFeel_V2::getOptionsForComboBoxPopupMenu (ComboBox& box, Label& label)
  963. {
  964. return PopupMenu::Options().withTargetComponent (&box)
  965. .withItemThatMustBeVisible (box.getSelectedId())
  966. .withMinimumWidth (box.getWidth())
  967. .withMaximumNumColumns (1)
  968. .withStandardItemHeight (label.getHeight());
  969. }
  970. //==============================================================================
  971. Font LookAndFeel_V2::getLabelFont (Label& label)
  972. {
  973. return label.getFont();
  974. }
  975. void LookAndFeel_V2::drawLabel (Graphics& g, Label& label)
  976. {
  977. g.fillAll (label.findColour (Label::backgroundColourId));
  978. if (! label.isBeingEdited())
  979. {
  980. auto alpha = label.isEnabled() ? 1.0f : 0.5f;
  981. const Font font (getLabelFont (label));
  982. g.setColour (label.findColour (Label::textColourId).withMultipliedAlpha (alpha));
  983. g.setFont (font);
  984. Rectangle<int> textArea (label.getBorderSize().subtractedFrom (label.getLocalBounds()));
  985. g.drawFittedText (label.getText(), textArea, label.getJustificationType(),
  986. jmax (1, (int) (textArea.getHeight() / font.getHeight())),
  987. label.getMinimumHorizontalScale());
  988. g.setColour (label.findColour (Label::outlineColourId).withMultipliedAlpha (alpha));
  989. }
  990. else if (label.isEnabled())
  991. {
  992. g.setColour (label.findColour (Label::outlineColourId));
  993. }
  994. g.drawRect (label.getLocalBounds());
  995. }
  996. //==============================================================================
  997. void LookAndFeel_V2::drawLinearSliderBackground (Graphics& g, int x, int y, int width, int height,
  998. float /*sliderPos*/,
  999. float /*minSliderPos*/,
  1000. float /*maxSliderPos*/,
  1001. const Slider::SliderStyle /*style*/, Slider& slider)
  1002. {
  1003. auto sliderRadius = (float) (getSliderThumbRadius (slider) - 2);
  1004. auto trackColour = slider.findColour (Slider::trackColourId);
  1005. auto gradCol1 = trackColour.overlaidWith (Colours::black.withAlpha (slider.isEnabled() ? 0.25f : 0.13f));
  1006. auto gradCol2 = trackColour.overlaidWith (Colour (0x14000000));
  1007. Path indent;
  1008. if (slider.isHorizontal())
  1009. {
  1010. const float iy = y + height * 0.5f - sliderRadius * 0.5f;
  1011. const float ih = sliderRadius;
  1012. g.setGradientFill (ColourGradient::vertical (gradCol1, iy, gradCol2, iy + ih));
  1013. indent.addRoundedRectangle (x - sliderRadius * 0.5f, iy,
  1014. width + sliderRadius, ih,
  1015. 5.0f);
  1016. }
  1017. else
  1018. {
  1019. const float ix = x + width * 0.5f - sliderRadius * 0.5f;
  1020. const float iw = sliderRadius;
  1021. g.setGradientFill (ColourGradient::horizontal (gradCol1, ix, gradCol2, ix + iw));
  1022. indent.addRoundedRectangle (ix, y - sliderRadius * 0.5f,
  1023. iw, height + sliderRadius,
  1024. 5.0f);
  1025. }
  1026. g.fillPath (indent);
  1027. g.setColour (Colour (0x4c000000));
  1028. g.strokePath (indent, PathStrokeType (0.5f));
  1029. }
  1030. void LookAndFeel_V2::drawLinearSliderThumb (Graphics& g, int x, int y, int width, int height,
  1031. float sliderPos, float minSliderPos, float maxSliderPos,
  1032. const Slider::SliderStyle style, Slider& slider)
  1033. {
  1034. auto sliderRadius = (float) (getSliderThumbRadius (slider) - 2);
  1035. auto knobColour = LookAndFeelHelpers::createBaseColour (slider.findColour (Slider::thumbColourId),
  1036. slider.hasKeyboardFocus (false) && slider.isEnabled(),
  1037. slider.isMouseOverOrDragging() && slider.isEnabled(),
  1038. slider.isMouseButtonDown() && slider.isEnabled());
  1039. const float outlineThickness = slider.isEnabled() ? 0.8f : 0.3f;
  1040. if (style == Slider::LinearHorizontal || style == Slider::LinearVertical)
  1041. {
  1042. float kx, ky;
  1043. if (style == Slider::LinearVertical)
  1044. {
  1045. kx = x + width * 0.5f;
  1046. ky = sliderPos;
  1047. }
  1048. else
  1049. {
  1050. kx = sliderPos;
  1051. ky = y + height * 0.5f;
  1052. }
  1053. drawGlassSphere (g,
  1054. kx - sliderRadius,
  1055. ky - sliderRadius,
  1056. sliderRadius * 2.0f,
  1057. knobColour, outlineThickness);
  1058. }
  1059. else
  1060. {
  1061. if (style == Slider::ThreeValueVertical)
  1062. {
  1063. drawGlassSphere (g, x + width * 0.5f - sliderRadius,
  1064. sliderPos - sliderRadius,
  1065. sliderRadius * 2.0f,
  1066. knobColour, outlineThickness);
  1067. }
  1068. else if (style == Slider::ThreeValueHorizontal)
  1069. {
  1070. drawGlassSphere (g,sliderPos - sliderRadius,
  1071. y + height * 0.5f - sliderRadius,
  1072. sliderRadius * 2.0f,
  1073. knobColour, outlineThickness);
  1074. }
  1075. if (style == Slider::TwoValueVertical || style == Slider::ThreeValueVertical)
  1076. {
  1077. auto sr = jmin (sliderRadius, width * 0.4f);
  1078. drawGlassPointer (g, jmax (0.0f, x + width * 0.5f - sliderRadius * 2.0f),
  1079. minSliderPos - sliderRadius,
  1080. sliderRadius * 2.0f, knobColour, outlineThickness, 1);
  1081. drawGlassPointer (g, jmin (x + width - sliderRadius * 2.0f, x + width * 0.5f), maxSliderPos - sr,
  1082. sliderRadius * 2.0f, knobColour, outlineThickness, 3);
  1083. }
  1084. else if (style == Slider::TwoValueHorizontal || style == Slider::ThreeValueHorizontal)
  1085. {
  1086. auto sr = jmin (sliderRadius, height * 0.4f);
  1087. drawGlassPointer (g, minSliderPos - sr,
  1088. jmax (0.0f, y + height * 0.5f - sliderRadius * 2.0f),
  1089. sliderRadius * 2.0f, knobColour, outlineThickness, 2);
  1090. drawGlassPointer (g, maxSliderPos - sliderRadius,
  1091. jmin (y + height - sliderRadius * 2.0f, y + height * 0.5f),
  1092. sliderRadius * 2.0f, knobColour, outlineThickness, 4);
  1093. }
  1094. }
  1095. }
  1096. void LookAndFeel_V2::drawLinearSlider (Graphics& g, int x, int y, int width, int height,
  1097. float sliderPos, float minSliderPos, float maxSliderPos,
  1098. const Slider::SliderStyle style, Slider& slider)
  1099. {
  1100. g.fillAll (slider.findColour (Slider::backgroundColourId));
  1101. if (style == Slider::LinearBar || style == Slider::LinearBarVertical)
  1102. {
  1103. const bool isMouseOver = slider.isMouseOverOrDragging() && slider.isEnabled();
  1104. auto baseColour = LookAndFeelHelpers::createBaseColour (slider.findColour (Slider::thumbColourId)
  1105. .withMultipliedSaturation (slider.isEnabled() ? 1.0f : 0.5f),
  1106. false, isMouseOver,
  1107. isMouseOver || slider.isMouseButtonDown());
  1108. drawShinyButtonShape (g,
  1109. (float) x,
  1110. style == Slider::LinearBarVertical ? sliderPos
  1111. : (float) y,
  1112. style == Slider::LinearBarVertical ? (float) width
  1113. : (sliderPos - x),
  1114. style == Slider::LinearBarVertical ? (height - sliderPos)
  1115. : (float) height, 0.0f,
  1116. baseColour,
  1117. slider.isEnabled() ? 0.9f : 0.3f,
  1118. true, true, true, true);
  1119. }
  1120. else
  1121. {
  1122. drawLinearSliderBackground (g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider);
  1123. drawLinearSliderThumb (g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider);
  1124. }
  1125. }
  1126. int LookAndFeel_V2::getSliderThumbRadius (Slider& slider)
  1127. {
  1128. return jmin (7,
  1129. slider.getHeight() / 2,
  1130. slider.getWidth() / 2) + 2;
  1131. }
  1132. void LookAndFeel_V2::drawRotarySlider (Graphics& g, int x, int y, int width, int height, float sliderPos,
  1133. const float rotaryStartAngle, const float rotaryEndAngle, Slider& slider)
  1134. {
  1135. const float radius = jmin (width / 2, height / 2) - 2.0f;
  1136. const float centreX = x + width * 0.5f;
  1137. const float centreY = y + height * 0.5f;
  1138. const float rx = centreX - radius;
  1139. const float ry = centreY - radius;
  1140. const float rw = radius * 2.0f;
  1141. const float angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
  1142. const bool isMouseOver = slider.isMouseOverOrDragging() && slider.isEnabled();
  1143. if (radius > 12.0f)
  1144. {
  1145. if (slider.isEnabled())
  1146. g.setColour (slider.findColour (Slider::rotarySliderFillColourId).withAlpha (isMouseOver ? 1.0f : 0.7f));
  1147. else
  1148. g.setColour (Colour (0x80808080));
  1149. const float thickness = 0.7f;
  1150. {
  1151. Path filledArc;
  1152. filledArc.addPieSegment (rx, ry, rw, rw, rotaryStartAngle, angle, thickness);
  1153. g.fillPath (filledArc);
  1154. }
  1155. {
  1156. const float innerRadius = radius * 0.2f;
  1157. Path p;
  1158. p.addTriangle (-innerRadius, 0.0f,
  1159. 0.0f, -radius * thickness * 1.1f,
  1160. innerRadius, 0.0f);
  1161. p.addEllipse (-innerRadius, -innerRadius, innerRadius * 2.0f, innerRadius * 2.0f);
  1162. g.fillPath (p, AffineTransform::rotation (angle).translated (centreX, centreY));
  1163. }
  1164. if (slider.isEnabled())
  1165. g.setColour (slider.findColour (Slider::rotarySliderOutlineColourId));
  1166. else
  1167. g.setColour (Colour (0x80808080));
  1168. Path outlineArc;
  1169. outlineArc.addPieSegment (rx, ry, rw, rw, rotaryStartAngle, rotaryEndAngle, thickness);
  1170. outlineArc.closeSubPath();
  1171. g.strokePath (outlineArc, PathStrokeType (slider.isEnabled() ? (isMouseOver ? 2.0f : 1.2f) : 0.3f));
  1172. }
  1173. else
  1174. {
  1175. if (slider.isEnabled())
  1176. g.setColour (slider.findColour (Slider::rotarySliderFillColourId).withAlpha (isMouseOver ? 1.0f : 0.7f));
  1177. else
  1178. g.setColour (Colour (0x80808080));
  1179. Path p;
  1180. p.addEllipse (-0.4f * rw, -0.4f * rw, rw * 0.8f, rw * 0.8f);
  1181. PathStrokeType (rw * 0.1f).createStrokedPath (p, p);
  1182. p.addLineSegment (Line<float> (0.0f, 0.0f, 0.0f, -radius), rw * 0.2f);
  1183. g.fillPath (p, AffineTransform::rotation (angle).translated (centreX, centreY));
  1184. }
  1185. }
  1186. Button* LookAndFeel_V2::createSliderButton (Slider&, const bool isIncrement)
  1187. {
  1188. return new TextButton (isIncrement ? "+" : "-", String());
  1189. }
  1190. class LookAndFeel_V2::SliderLabelComp : public Label
  1191. {
  1192. public:
  1193. SliderLabelComp() : Label ({}, {}) {}
  1194. void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override {}
  1195. };
  1196. Label* LookAndFeel_V2::createSliderTextBox (Slider& slider)
  1197. {
  1198. auto l = new SliderLabelComp();
  1199. l->setJustificationType (Justification::centred);
  1200. l->setKeyboardType (TextInputTarget::decimalKeyboard);
  1201. l->setColour (Label::textColourId, slider.findColour (Slider::textBoxTextColourId));
  1202. l->setColour (Label::backgroundColourId,
  1203. (slider.getSliderStyle() == Slider::LinearBar || slider.getSliderStyle() == Slider::LinearBarVertical)
  1204. ? Colours::transparentBlack
  1205. : slider.findColour (Slider::textBoxBackgroundColourId));
  1206. l->setColour (Label::outlineColourId, slider.findColour (Slider::textBoxOutlineColourId));
  1207. l->setColour (TextEditor::textColourId, slider.findColour (Slider::textBoxTextColourId));
  1208. l->setColour (TextEditor::backgroundColourId,
  1209. slider.findColour (Slider::textBoxBackgroundColourId)
  1210. .withAlpha ((slider.getSliderStyle() == Slider::LinearBar || slider.getSliderStyle() == Slider::LinearBarVertical)
  1211. ? 0.7f : 1.0f));
  1212. l->setColour (TextEditor::outlineColourId, slider.findColour (Slider::textBoxOutlineColourId));
  1213. l->setColour (TextEditor::highlightColourId, slider.findColour (Slider::textBoxHighlightColourId));
  1214. return l;
  1215. }
  1216. ImageEffectFilter* LookAndFeel_V2::getSliderEffect (Slider&)
  1217. {
  1218. return nullptr;
  1219. }
  1220. Font LookAndFeel_V2::getSliderPopupFont (Slider&)
  1221. {
  1222. return Font (15.0f, Font::bold);
  1223. }
  1224. int LookAndFeel_V2::getSliderPopupPlacement (Slider&)
  1225. {
  1226. return BubbleComponent::above
  1227. | BubbleComponent::below
  1228. | BubbleComponent::left
  1229. | BubbleComponent::right;
  1230. }
  1231. //==============================================================================
  1232. Slider::SliderLayout LookAndFeel_V2::getSliderLayout (Slider& slider)
  1233. {
  1234. // 1. compute the actually visible textBox size from the slider textBox size and some additional constraints
  1235. int minXSpace = 0;
  1236. int minYSpace = 0;
  1237. auto textBoxPos = slider.getTextBoxPosition();
  1238. if (textBoxPos == Slider::TextBoxLeft || textBoxPos == Slider::TextBoxRight)
  1239. minXSpace = 30;
  1240. else
  1241. minYSpace = 15;
  1242. auto localBounds = slider.getLocalBounds();
  1243. auto textBoxWidth = jmax (0, jmin (slider.getTextBoxWidth(), localBounds.getWidth() - minXSpace));
  1244. auto textBoxHeight = jmax (0, jmin (slider.getTextBoxHeight(), localBounds.getHeight() - minYSpace));
  1245. Slider::SliderLayout layout;
  1246. // 2. set the textBox bounds
  1247. if (textBoxPos != Slider::NoTextBox)
  1248. {
  1249. if (slider.isBar())
  1250. {
  1251. layout.textBoxBounds = localBounds;
  1252. }
  1253. else
  1254. {
  1255. layout.textBoxBounds.setWidth (textBoxWidth);
  1256. layout.textBoxBounds.setHeight (textBoxHeight);
  1257. if (textBoxPos == Slider::TextBoxLeft) layout.textBoxBounds.setX (0);
  1258. else if (textBoxPos == Slider::TextBoxRight) layout.textBoxBounds.setX (localBounds.getWidth() - textBoxWidth);
  1259. else /* above or below -> centre horizontally */ layout.textBoxBounds.setX ((localBounds.getWidth() - textBoxWidth) / 2);
  1260. if (textBoxPos == Slider::TextBoxAbove) layout.textBoxBounds.setY (0);
  1261. else if (textBoxPos == Slider::TextBoxBelow) layout.textBoxBounds.setY (localBounds.getHeight() - textBoxHeight);
  1262. else /* left or right -> centre vertically */ layout.textBoxBounds.setY ((localBounds.getHeight() - textBoxHeight) / 2);
  1263. }
  1264. }
  1265. // 3. set the slider bounds
  1266. layout.sliderBounds = localBounds;
  1267. if (slider.isBar())
  1268. {
  1269. layout.sliderBounds.reduce (1, 1); // bar border
  1270. }
  1271. else
  1272. {
  1273. if (textBoxPos == Slider::TextBoxLeft) layout.sliderBounds.removeFromLeft (textBoxWidth);
  1274. else if (textBoxPos == Slider::TextBoxRight) layout.sliderBounds.removeFromRight (textBoxWidth);
  1275. else if (textBoxPos == Slider::TextBoxAbove) layout.sliderBounds.removeFromTop (textBoxHeight);
  1276. else if (textBoxPos == Slider::TextBoxBelow) layout.sliderBounds.removeFromBottom (textBoxHeight);
  1277. const int thumbIndent = getSliderThumbRadius (slider);
  1278. if (slider.isHorizontal()) layout.sliderBounds.reduce (thumbIndent, 0);
  1279. else if (slider.isVertical()) layout.sliderBounds.reduce (0, thumbIndent);
  1280. }
  1281. return layout;
  1282. }
  1283. //==============================================================================
  1284. Rectangle<int> LookAndFeel_V2::getTooltipBounds (const String& tipText, Point<int> screenPos, Rectangle<int> parentArea)
  1285. {
  1286. const TextLayout tl (LookAndFeelHelpers::layoutTooltipText (tipText, Colours::black));
  1287. auto w = (int) (tl.getWidth() + 14.0f);
  1288. auto h = (int) (tl.getHeight() + 6.0f);
  1289. return Rectangle<int> (screenPos.x > parentArea.getCentreX() ? screenPos.x - (w + 12) : screenPos.x + 24,
  1290. screenPos.y > parentArea.getCentreY() ? screenPos.y - (h + 6) : screenPos.y + 6,
  1291. w, h)
  1292. .constrainedWithin (parentArea);
  1293. }
  1294. void LookAndFeel_V2::drawTooltip (Graphics& g, const String& text, int width, int height)
  1295. {
  1296. g.fillAll (findColour (TooltipWindow::backgroundColourId));
  1297. #if ! JUCE_MAC // The mac windows already have a non-optional 1 pix outline, so don't double it here..
  1298. g.setColour (findColour (TooltipWindow::outlineColourId));
  1299. g.drawRect (0, 0, width, height, 1);
  1300. #endif
  1301. LookAndFeelHelpers::layoutTooltipText (text, findColour (TooltipWindow::textColourId))
  1302. .draw (g, Rectangle<float> ((float) width, (float) height));
  1303. }
  1304. //==============================================================================
  1305. Button* LookAndFeel_V2::createFilenameComponentBrowseButton (const String& text)
  1306. {
  1307. return new TextButton (text, TRANS("click to browse for a different file"));
  1308. }
  1309. void LookAndFeel_V2::layoutFilenameComponent (FilenameComponent& filenameComp,
  1310. ComboBox* filenameBox, Button* browseButton)
  1311. {
  1312. browseButton->setSize (80, filenameComp.getHeight());
  1313. if (auto* tb = dynamic_cast<TextButton*> (browseButton))
  1314. tb->changeWidthToFitText();
  1315. browseButton->setTopRightPosition (filenameComp.getWidth(), 0);
  1316. filenameBox->setBounds (0, 0, browseButton->getX(), filenameComp.getHeight());
  1317. }
  1318. //==============================================================================
  1319. void LookAndFeel_V2::drawConcertinaPanelHeader (Graphics& g, const Rectangle<int>& area,
  1320. bool isMouseOver, bool /*isMouseDown*/,
  1321. ConcertinaPanel&, Component& panel)
  1322. {
  1323. g.fillAll (Colours::grey.withAlpha (isMouseOver ? 0.9f : 0.7f));
  1324. g.setColour (Colours::black.withAlpha (0.5f));
  1325. g.drawRect (area);
  1326. g.setColour (Colours::white);
  1327. g.setFont (Font (area.getHeight() * 0.7f).boldened());
  1328. g.drawFittedText (panel.getName(), 4, 0, area.getWidth() - 6, area.getHeight(), Justification::centredLeft, 1);
  1329. }
  1330. //==============================================================================
  1331. void LookAndFeel_V2::drawImageButton (Graphics& g, Image* image,
  1332. int imageX, int imageY, int imageW, int imageH,
  1333. const Colour& overlayColour,
  1334. float imageOpacity,
  1335. ImageButton& button)
  1336. {
  1337. if (! button.isEnabled())
  1338. imageOpacity *= 0.3f;
  1339. AffineTransform t = RectanglePlacement (RectanglePlacement::stretchToFit)
  1340. .getTransformToFit (image->getBounds().toFloat(),
  1341. Rectangle<int> (imageX, imageY, imageW, imageH).toFloat());
  1342. if (! overlayColour.isOpaque())
  1343. {
  1344. g.setOpacity (imageOpacity);
  1345. g.drawImageTransformed (*image, t, false);
  1346. }
  1347. if (! overlayColour.isTransparent())
  1348. {
  1349. g.setColour (overlayColour);
  1350. g.drawImageTransformed (*image, t, true);
  1351. }
  1352. }
  1353. //==============================================================================
  1354. void LookAndFeel_V2::drawCornerResizer (Graphics& g, int w, int h, bool /*isMouseOver*/, bool /*isMouseDragging*/)
  1355. {
  1356. auto lineThickness = jmin (w, h) * 0.075f;
  1357. for (float i = 0.0f; i < 1.0f; i += 0.3f)
  1358. {
  1359. g.setColour (Colours::lightgrey);
  1360. g.drawLine (w * i,
  1361. h + 1.0f,
  1362. w + 1.0f,
  1363. h * i,
  1364. lineThickness);
  1365. g.setColour (Colours::darkgrey);
  1366. g.drawLine (w * i + lineThickness,
  1367. h + 1.0f,
  1368. w + 1.0f,
  1369. h * i + lineThickness,
  1370. lineThickness);
  1371. }
  1372. }
  1373. void LookAndFeel_V2::drawResizableFrame (Graphics& g, int w, int h, const BorderSize<int>& border)
  1374. {
  1375. if (! border.isEmpty())
  1376. {
  1377. const Rectangle<int> fullSize (0, 0, w, h);
  1378. auto centreArea = border.subtractedFrom (fullSize);
  1379. g.saveState();
  1380. g.excludeClipRegion (centreArea);
  1381. g.setColour (Colour (0x50000000));
  1382. g.drawRect (fullSize);
  1383. g.setColour (Colour (0x19000000));
  1384. g.drawRect (centreArea.expanded (1, 1));
  1385. g.restoreState();
  1386. }
  1387. }
  1388. //==============================================================================
  1389. void LookAndFeel_V2::fillResizableWindowBackground (Graphics& g, int /*w*/, int /*h*/,
  1390. const BorderSize<int>& /*border*/, ResizableWindow& window)
  1391. {
  1392. g.fillAll (window.getBackgroundColour());
  1393. }
  1394. void LookAndFeel_V2::drawResizableWindowBorder (Graphics&, int /*w*/, int /*h*/,
  1395. const BorderSize<int>& /*border*/, ResizableWindow&)
  1396. {
  1397. }
  1398. void LookAndFeel_V2::drawDocumentWindowTitleBar (DocumentWindow& window, Graphics& g,
  1399. int w, int h, int titleSpaceX, int titleSpaceW,
  1400. const Image* icon, bool drawTitleTextOnLeft)
  1401. {
  1402. if (w * h == 0)
  1403. return;
  1404. const bool isActive = window.isActiveWindow();
  1405. g.setGradientFill (ColourGradient::vertical (window.getBackgroundColour(), 0,
  1406. window.getBackgroundColour().contrasting (isActive ? 0.15f : 0.05f), (float) h));
  1407. g.fillAll();
  1408. Font font (h * 0.65f, Font::bold);
  1409. g.setFont (font);
  1410. int textW = font.getStringWidth (window.getName());
  1411. int iconW = 0;
  1412. int iconH = 0;
  1413. if (icon != nullptr)
  1414. {
  1415. iconH = (int) font.getHeight();
  1416. iconW = icon->getWidth() * iconH / icon->getHeight() + 4;
  1417. }
  1418. textW = jmin (titleSpaceW, textW + iconW);
  1419. int textX = drawTitleTextOnLeft ? titleSpaceX
  1420. : jmax (titleSpaceX, (w - textW) / 2);
  1421. if (textX + textW > titleSpaceX + titleSpaceW)
  1422. textX = titleSpaceX + titleSpaceW - textW;
  1423. if (icon != nullptr)
  1424. {
  1425. g.setOpacity (isActive ? 1.0f : 0.6f);
  1426. g.drawImageWithin (*icon, textX, (h - iconH) / 2, iconW, iconH,
  1427. RectanglePlacement::centred, false);
  1428. textX += iconW;
  1429. textW -= iconW;
  1430. }
  1431. if (window.isColourSpecified (DocumentWindow::textColourId) || isColourSpecified (DocumentWindow::textColourId))
  1432. g.setColour (window.findColour (DocumentWindow::textColourId));
  1433. else
  1434. g.setColour (window.getBackgroundColour().contrasting (isActive ? 0.7f : 0.4f));
  1435. g.drawText (window.getName(), textX, 0, textW, h, Justification::centredLeft, true);
  1436. }
  1437. //==============================================================================
  1438. class LookAndFeel_V2::GlassWindowButton : public Button
  1439. {
  1440. public:
  1441. GlassWindowButton (const String& name, Colour col,
  1442. const Path& normalShape_,
  1443. const Path& toggledShape_) noexcept
  1444. : Button (name),
  1445. colour (col),
  1446. normalShape (normalShape_),
  1447. toggledShape (toggledShape_)
  1448. {
  1449. }
  1450. //==============================================================================
  1451. void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override
  1452. {
  1453. float alpha = isMouseOverButton ? (isButtonDown ? 1.0f : 0.8f) : 0.55f;
  1454. if (! isEnabled())
  1455. alpha *= 0.5f;
  1456. float x = 0, y = 0, diam;
  1457. if (getWidth() < getHeight())
  1458. {
  1459. diam = (float) getWidth();
  1460. y = (getHeight() - getWidth()) * 0.5f;
  1461. }
  1462. else
  1463. {
  1464. diam = (float) getHeight();
  1465. y = (getWidth() - getHeight()) * 0.5f;
  1466. }
  1467. x += diam * 0.05f;
  1468. y += diam * 0.05f;
  1469. diam *= 0.9f;
  1470. g.setGradientFill (ColourGradient (Colour::greyLevel (0.9f).withAlpha (alpha), 0, y + diam,
  1471. Colour::greyLevel (0.6f).withAlpha (alpha), 0, y, false));
  1472. g.fillEllipse (x, y, diam, diam);
  1473. x += 2.0f;
  1474. y += 2.0f;
  1475. diam -= 4.0f;
  1476. LookAndFeel_V2::drawGlassSphere (g, x, y, diam, colour.withAlpha (alpha), 1.0f);
  1477. Path& p = getToggleState() ? toggledShape : normalShape;
  1478. const AffineTransform t (p.getTransformToScaleToFit (x + diam * 0.3f, y + diam * 0.3f,
  1479. diam * 0.4f, diam * 0.4f, true));
  1480. g.setColour (Colours::black.withAlpha (alpha * 0.6f));
  1481. g.fillPath (p, t);
  1482. }
  1483. private:
  1484. Colour colour;
  1485. Path normalShape, toggledShape;
  1486. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GlassWindowButton)
  1487. };
  1488. Button* LookAndFeel_V2::createDocumentWindowButton (int buttonType)
  1489. {
  1490. Path shape;
  1491. const float crossThickness = 0.25f;
  1492. if (buttonType == DocumentWindow::closeButton)
  1493. {
  1494. shape.addLineSegment (Line<float> (0.0f, 0.0f, 1.0f, 1.0f), crossThickness * 1.4f);
  1495. shape.addLineSegment (Line<float> (1.0f, 0.0f, 0.0f, 1.0f), crossThickness * 1.4f);
  1496. return new GlassWindowButton ("close", Colour (0xffdd1100), shape, shape);
  1497. }
  1498. if (buttonType == DocumentWindow::minimiseButton)
  1499. {
  1500. shape.addLineSegment (Line<float> (0.0f, 0.5f, 1.0f, 0.5f), crossThickness);
  1501. return new GlassWindowButton ("minimise", Colour (0xffaa8811), shape, shape);
  1502. }
  1503. if (buttonType == DocumentWindow::maximiseButton)
  1504. {
  1505. shape.addLineSegment (Line<float> (0.5f, 0.0f, 0.5f, 1.0f), crossThickness);
  1506. shape.addLineSegment (Line<float> (0.0f, 0.5f, 1.0f, 0.5f), crossThickness);
  1507. Path fullscreenShape;
  1508. fullscreenShape.startNewSubPath (45.0f, 100.0f);
  1509. fullscreenShape.lineTo (0.0f, 100.0f);
  1510. fullscreenShape.lineTo (0.0f, 0.0f);
  1511. fullscreenShape.lineTo (100.0f, 0.0f);
  1512. fullscreenShape.lineTo (100.0f, 45.0f);
  1513. fullscreenShape.addRectangle (45.0f, 45.0f, 100.0f, 100.0f);
  1514. PathStrokeType (30.0f).createStrokedPath (fullscreenShape, fullscreenShape);
  1515. return new GlassWindowButton ("maximise", Colour (0xff119911), shape, fullscreenShape);
  1516. }
  1517. jassertfalse;
  1518. return nullptr;
  1519. }
  1520. void LookAndFeel_V2::positionDocumentWindowButtons (DocumentWindow&,
  1521. int titleBarX, int titleBarY,
  1522. int titleBarW, int titleBarH,
  1523. Button* minimiseButton,
  1524. Button* maximiseButton,
  1525. Button* closeButton,
  1526. bool positionTitleBarButtonsOnLeft)
  1527. {
  1528. const int buttonW = titleBarH - titleBarH / 8;
  1529. int x = positionTitleBarButtonsOnLeft ? titleBarX + 4
  1530. : titleBarX + titleBarW - buttonW - buttonW / 4;
  1531. if (closeButton != nullptr)
  1532. {
  1533. closeButton->setBounds (x, titleBarY, buttonW, titleBarH);
  1534. x += positionTitleBarButtonsOnLeft ? buttonW : -(buttonW + buttonW / 4);
  1535. }
  1536. if (positionTitleBarButtonsOnLeft)
  1537. std::swap (minimiseButton, maximiseButton);
  1538. if (maximiseButton != nullptr)
  1539. {
  1540. maximiseButton->setBounds (x, titleBarY, buttonW, titleBarH);
  1541. x += positionTitleBarButtonsOnLeft ? buttonW : -buttonW;
  1542. }
  1543. if (minimiseButton != nullptr)
  1544. minimiseButton->setBounds (x, titleBarY, buttonW, titleBarH);
  1545. }
  1546. int LookAndFeel_V2::getDefaultMenuBarHeight()
  1547. {
  1548. return 24;
  1549. }
  1550. //==============================================================================
  1551. DropShadower* LookAndFeel_V2::createDropShadowerForComponent (Component*)
  1552. {
  1553. return new DropShadower (DropShadow (Colours::black.withAlpha (0.4f), 10, Point<int> (0, 2)));
  1554. }
  1555. //==============================================================================
  1556. void LookAndFeel_V2::drawStretchableLayoutResizerBar (Graphics& g, int w, int h,
  1557. bool /*isVerticalBar*/,
  1558. bool isMouseOver,
  1559. bool isMouseDragging)
  1560. {
  1561. auto alpha = 0.5f;
  1562. if (isMouseOver || isMouseDragging)
  1563. {
  1564. g.fillAll (Colour (0x190000ff));
  1565. alpha = 1.0f;
  1566. }
  1567. auto cx = w * 0.5f;
  1568. auto cy = h * 0.5f;
  1569. auto cr = jmin (w, h) * 0.4f;
  1570. g.setGradientFill (ColourGradient (Colours::white.withAlpha (alpha), cx + cr * 0.1f, cy + cr,
  1571. Colours::black.withAlpha (alpha), cx, cy - cr * 4.0f,
  1572. true));
  1573. g.fillEllipse (cx - cr, cy - cr, cr * 2.0f, cr * 2.0f);
  1574. }
  1575. //==============================================================================
  1576. void LookAndFeel_V2::drawGroupComponentOutline (Graphics& g, int width, int height,
  1577. const String& text, const Justification& position,
  1578. GroupComponent& group)
  1579. {
  1580. const float textH = 15.0f;
  1581. const float indent = 3.0f;
  1582. const float textEdgeGap = 4.0f;
  1583. auto cs = 5.0f;
  1584. Font f (textH);
  1585. Path p;
  1586. auto x = indent;
  1587. auto y = f.getAscent() - 3.0f;
  1588. auto w = jmax (0.0f, width - x * 2.0f);
  1589. auto h = jmax (0.0f, height - y - indent);
  1590. cs = jmin (cs, w * 0.5f, h * 0.5f);
  1591. auto cs2 = 2.0f * cs;
  1592. auto textW = text.isEmpty() ? 0 : jlimit (0.0f, jmax (0.0f, w - cs2 - textEdgeGap * 2), f.getStringWidth (text) + textEdgeGap * 2.0f);
  1593. auto textX = cs + textEdgeGap;
  1594. if (position.testFlags (Justification::horizontallyCentred))
  1595. textX = cs + (w - cs2 - textW) * 0.5f;
  1596. else if (position.testFlags (Justification::right))
  1597. textX = w - cs - textW - textEdgeGap;
  1598. p.startNewSubPath (x + textX + textW, y);
  1599. p.lineTo (x + w - cs, y);
  1600. p.addArc (x + w - cs2, y, cs2, cs2, 0, MathConstants<float>::halfPi);
  1601. p.lineTo (x + w, y + h - cs);
  1602. p.addArc (x + w - cs2, y + h - cs2, cs2, cs2, MathConstants<float>::halfPi, MathConstants<float>::pi);
  1603. p.lineTo (x + cs, y + h);
  1604. p.addArc (x, y + h - cs2, cs2, cs2, MathConstants<float>::pi, MathConstants<float>::pi * 1.5f);
  1605. p.lineTo (x, y + cs);
  1606. p.addArc (x, y, cs2, cs2, MathConstants<float>::pi * 1.5f, MathConstants<float>::twoPi);
  1607. p.lineTo (x + textX, y);
  1608. auto alpha = group.isEnabled() ? 1.0f : 0.5f;
  1609. g.setColour (group.findColour (GroupComponent::outlineColourId)
  1610. .withMultipliedAlpha (alpha));
  1611. g.strokePath (p, PathStrokeType (2.0f));
  1612. g.setColour (group.findColour (GroupComponent::textColourId)
  1613. .withMultipliedAlpha (alpha));
  1614. g.setFont (f);
  1615. g.drawText (text,
  1616. roundToInt (x + textX), 0,
  1617. roundToInt (textW),
  1618. roundToInt (textH),
  1619. Justification::centred, true);
  1620. }
  1621. //==============================================================================
  1622. int LookAndFeel_V2::getTabButtonOverlap (int tabDepth)
  1623. {
  1624. return 1 + tabDepth / 3;
  1625. }
  1626. int LookAndFeel_V2::getTabButtonSpaceAroundImage()
  1627. {
  1628. return 4;
  1629. }
  1630. int LookAndFeel_V2::getTabButtonBestWidth (TabBarButton& button, int tabDepth)
  1631. {
  1632. int width = Font (tabDepth * 0.6f).getStringWidth (button.getButtonText().trim())
  1633. + getTabButtonOverlap (tabDepth) * 2;
  1634. if (auto* extraComponent = button.getExtraComponent())
  1635. width += button.getTabbedButtonBar().isVertical() ? extraComponent->getHeight()
  1636. : extraComponent->getWidth();
  1637. return jlimit (tabDepth * 2, tabDepth * 8, width);
  1638. }
  1639. Rectangle<int> LookAndFeel_V2::getTabButtonExtraComponentBounds (const TabBarButton& button, Rectangle<int>& textArea, Component& comp)
  1640. {
  1641. Rectangle<int> extraComp;
  1642. auto orientation = button.getTabbedButtonBar().getOrientation();
  1643. if (button.getExtraComponentPlacement() == TabBarButton::beforeText)
  1644. {
  1645. switch (orientation)
  1646. {
  1647. case TabbedButtonBar::TabsAtBottom:
  1648. case TabbedButtonBar::TabsAtTop: extraComp = textArea.removeFromLeft (comp.getWidth()); break;
  1649. case TabbedButtonBar::TabsAtLeft: extraComp = textArea.removeFromBottom (comp.getHeight()); break;
  1650. case TabbedButtonBar::TabsAtRight: extraComp = textArea.removeFromTop (comp.getHeight()); break;
  1651. default: jassertfalse; break;
  1652. }
  1653. }
  1654. else
  1655. {
  1656. switch (orientation)
  1657. {
  1658. case TabbedButtonBar::TabsAtBottom:
  1659. case TabbedButtonBar::TabsAtTop: extraComp = textArea.removeFromRight (comp.getWidth()); break;
  1660. case TabbedButtonBar::TabsAtLeft: extraComp = textArea.removeFromTop (comp.getHeight()); break;
  1661. case TabbedButtonBar::TabsAtRight: extraComp = textArea.removeFromBottom (comp.getHeight()); break;
  1662. default: jassertfalse; break;
  1663. }
  1664. }
  1665. return extraComp;
  1666. }
  1667. void LookAndFeel_V2::createTabButtonShape (TabBarButton& button, Path& p, bool /*isMouseOver*/, bool /*isMouseDown*/)
  1668. {
  1669. auto activeArea = button.getActiveArea();
  1670. auto w = (float) activeArea.getWidth();
  1671. auto h = (float) activeArea.getHeight();
  1672. auto length = w;
  1673. auto depth = h;
  1674. if (button.getTabbedButtonBar().isVertical())
  1675. std::swap (length, depth);
  1676. const float indent = (float) getTabButtonOverlap ((int) depth);
  1677. const float overhang = 4.0f;
  1678. switch (button.getTabbedButtonBar().getOrientation())
  1679. {
  1680. case TabbedButtonBar::TabsAtLeft:
  1681. p.startNewSubPath (w, 0.0f);
  1682. p.lineTo (0.0f, indent);
  1683. p.lineTo (0.0f, h - indent);
  1684. p.lineTo (w, h);
  1685. p.lineTo (w + overhang, h + overhang);
  1686. p.lineTo (w + overhang, -overhang);
  1687. break;
  1688. case TabbedButtonBar::TabsAtRight:
  1689. p.startNewSubPath (0.0f, 0.0f);
  1690. p.lineTo (w, indent);
  1691. p.lineTo (w, h - indent);
  1692. p.lineTo (0.0f, h);
  1693. p.lineTo (-overhang, h + overhang);
  1694. p.lineTo (-overhang, -overhang);
  1695. break;
  1696. case TabbedButtonBar::TabsAtBottom:
  1697. p.startNewSubPath (0.0f, 0.0f);
  1698. p.lineTo (indent, h);
  1699. p.lineTo (w - indent, h);
  1700. p.lineTo (w, 0.0f);
  1701. p.lineTo (w + overhang, -overhang);
  1702. p.lineTo (-overhang, -overhang);
  1703. break;
  1704. default:
  1705. p.startNewSubPath (0.0f, h);
  1706. p.lineTo (indent, 0.0f);
  1707. p.lineTo (w - indent, 0.0f);
  1708. p.lineTo (w, h);
  1709. p.lineTo (w + overhang, h + overhang);
  1710. p.lineTo (-overhang, h + overhang);
  1711. break;
  1712. }
  1713. p.closeSubPath();
  1714. p = p.createPathWithRoundedCorners (3.0f);
  1715. }
  1716. void LookAndFeel_V2::fillTabButtonShape (TabBarButton& button, Graphics& g, const Path& path,
  1717. bool /*isMouseOver*/, bool /*isMouseDown*/)
  1718. {
  1719. auto tabBackground = button.getTabBackgroundColour();
  1720. const bool isFrontTab = button.isFrontTab();
  1721. g.setColour (isFrontTab ? tabBackground
  1722. : tabBackground.withMultipliedAlpha (0.9f));
  1723. g.fillPath (path);
  1724. g.setColour (button.findColour (isFrontTab ? TabbedButtonBar::frontOutlineColourId
  1725. : TabbedButtonBar::tabOutlineColourId, false)
  1726. .withMultipliedAlpha (button.isEnabled() ? 1.0f : 0.5f));
  1727. g.strokePath (path, PathStrokeType (isFrontTab ? 1.0f : 0.5f));
  1728. }
  1729. Font LookAndFeel_V2::getTabButtonFont (TabBarButton&, float height)
  1730. {
  1731. return { height * 0.6f };
  1732. }
  1733. void LookAndFeel_V2::drawTabButtonText (TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown)
  1734. {
  1735. auto area = button.getTextArea().toFloat();
  1736. auto length = area.getWidth();
  1737. auto depth = area.getHeight();
  1738. if (button.getTabbedButtonBar().isVertical())
  1739. std::swap (length, depth);
  1740. Font font (getTabButtonFont (button, depth));
  1741. font.setUnderline (button.hasKeyboardFocus (false));
  1742. AffineTransform t;
  1743. switch (button.getTabbedButtonBar().getOrientation())
  1744. {
  1745. case TabbedButtonBar::TabsAtLeft: t = t.rotated (MathConstants<float>::pi * -0.5f).translated (area.getX(), area.getBottom()); break;
  1746. case TabbedButtonBar::TabsAtRight: t = t.rotated (MathConstants<float>::pi * 0.5f).translated (area.getRight(), area.getY()); break;
  1747. case TabbedButtonBar::TabsAtTop:
  1748. case TabbedButtonBar::TabsAtBottom: t = t.translated (area.getX(), area.getY()); break;
  1749. default: jassertfalse; break;
  1750. }
  1751. Colour col;
  1752. if (button.isFrontTab() && (button.isColourSpecified (TabbedButtonBar::frontTextColourId)
  1753. || isColourSpecified (TabbedButtonBar::frontTextColourId)))
  1754. col = findColour (TabbedButtonBar::frontTextColourId);
  1755. else if (button.isColourSpecified (TabbedButtonBar::tabTextColourId)
  1756. || isColourSpecified (TabbedButtonBar::tabTextColourId))
  1757. col = findColour (TabbedButtonBar::tabTextColourId);
  1758. else
  1759. col = button.getTabBackgroundColour().contrasting();
  1760. auto alpha = button.isEnabled() ? ((isMouseOver || isMouseDown) ? 1.0f : 0.8f) : 0.3f;
  1761. g.setColour (col.withMultipliedAlpha (alpha));
  1762. g.setFont (font);
  1763. g.addTransform (t);
  1764. g.drawFittedText (button.getButtonText().trim(),
  1765. 0, 0, (int) length, (int) depth,
  1766. Justification::centred,
  1767. jmax (1, ((int) depth) / 12));
  1768. }
  1769. void LookAndFeel_V2::drawTabButton (TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown)
  1770. {
  1771. Path tabShape;
  1772. createTabButtonShape (button, tabShape, isMouseOver, isMouseDown);
  1773. auto activeArea = button.getActiveArea();
  1774. tabShape.applyTransform (AffineTransform::translation ((float) activeArea.getX(),
  1775. (float) activeArea.getY()));
  1776. DropShadow (Colours::black.withAlpha (0.5f), 2, Point<int> (0, 1)).drawForPath (g, tabShape);
  1777. fillTabButtonShape (button, g, tabShape, isMouseOver, isMouseDown);
  1778. drawTabButtonText (button, g, isMouseOver, isMouseDown);
  1779. }
  1780. void LookAndFeel_V2::drawTabbedButtonBarBackground (TabbedButtonBar&, Graphics&) {}
  1781. void LookAndFeel_V2::drawTabAreaBehindFrontButton (TabbedButtonBar& bar, Graphics& g, const int w, const int h)
  1782. {
  1783. auto shadowSize = 0.2f;
  1784. Rectangle<int> shadowRect, line;
  1785. ColourGradient gradient (Colours::black.withAlpha (bar.isEnabled() ? 0.25f : 0.15f), 0, 0,
  1786. Colours::transparentBlack, 0, 0, false);
  1787. switch (bar.getOrientation())
  1788. {
  1789. case TabbedButtonBar::TabsAtLeft:
  1790. gradient.point1.x = (float) w;
  1791. gradient.point2.x = w * (1.0f - shadowSize);
  1792. shadowRect.setBounds ((int) gradient.point2.x, 0, w - (int) gradient.point2.x, h);
  1793. line.setBounds (w - 1, 0, 1, h);
  1794. break;
  1795. case TabbedButtonBar::TabsAtRight:
  1796. gradient.point2.x = w * shadowSize;
  1797. shadowRect.setBounds (0, 0, (int) gradient.point2.x, h);
  1798. line.setBounds (0, 0, 1, h);
  1799. break;
  1800. case TabbedButtonBar::TabsAtTop:
  1801. gradient.point1.y = (float) h;
  1802. gradient.point2.y = h * (1.0f - shadowSize);
  1803. shadowRect.setBounds (0, (int) gradient.point2.y, w, h - (int) gradient.point2.y);
  1804. line.setBounds (0, h - 1, w, 1);
  1805. break;
  1806. case TabbedButtonBar::TabsAtBottom:
  1807. gradient.point2.y = h * shadowSize;
  1808. shadowRect.setBounds (0, 0, w, (int) gradient.point2.y);
  1809. line.setBounds (0, 0, w, 1);
  1810. break;
  1811. default: break;
  1812. }
  1813. g.setGradientFill (gradient);
  1814. g.fillRect (shadowRect.expanded (2, 2));
  1815. g.setColour (Colour (0x80000000));
  1816. g.fillRect (line);
  1817. }
  1818. Button* LookAndFeel_V2::createTabBarExtrasButton()
  1819. {
  1820. auto thickness = 7.0f;
  1821. auto indent = 22.0f;
  1822. Path p;
  1823. p.addEllipse (-10.0f, -10.0f, 120.0f, 120.0f);
  1824. DrawablePath ellipse;
  1825. ellipse.setPath (p);
  1826. ellipse.setFill (Colour (0x99ffffff));
  1827. p.clear();
  1828. p.addEllipse (0.0f, 0.0f, 100.0f, 100.0f);
  1829. p.addRectangle (indent, 50.0f - thickness, 100.0f - indent * 2.0f, thickness * 2.0f);
  1830. p.addRectangle (50.0f - thickness, indent, thickness * 2.0f, 50.0f - indent - thickness);
  1831. p.addRectangle (50.0f - thickness, 50.0f + thickness, thickness * 2.0f, 50.0f - indent - thickness);
  1832. p.setUsingNonZeroWinding (false);
  1833. DrawablePath dp;
  1834. dp.setPath (p);
  1835. dp.setFill (Colour (0x59000000));
  1836. DrawableComposite normalImage;
  1837. normalImage.addAndMakeVisible (ellipse.createCopy());
  1838. normalImage.addAndMakeVisible (dp.createCopy());
  1839. dp.setFill (Colour (0xcc000000));
  1840. DrawableComposite overImage;
  1841. overImage.addAndMakeVisible (ellipse.createCopy());
  1842. overImage.addAndMakeVisible (dp.createCopy());
  1843. auto db = new DrawableButton ("tabs", DrawableButton::ImageFitted);
  1844. db->setImages (&normalImage, &overImage, nullptr);
  1845. return db;
  1846. }
  1847. //==============================================================================
  1848. void LookAndFeel_V2::drawTableHeaderBackground (Graphics& g, TableHeaderComponent& header)
  1849. {
  1850. g.fillAll (Colours::white);
  1851. auto area = header.getLocalBounds();
  1852. area.removeFromTop (area.getHeight() / 2);
  1853. auto backgroundColour = header.findColour (TableHeaderComponent::backgroundColourId);
  1854. g.setGradientFill (ColourGradient (backgroundColour,
  1855. 0.0f, (float) area.getY(),
  1856. backgroundColour.withMultipliedSaturation (.5f),
  1857. 0.0f, (float) area.getBottom(),
  1858. false));
  1859. g.fillRect (area);
  1860. g.setColour (header.findColour (TableHeaderComponent::outlineColourId));
  1861. g.fillRect (area.removeFromBottom (1));
  1862. for (int i = header.getNumColumns (true); --i >= 0;)
  1863. g.fillRect (header.getColumnPosition (i).removeFromRight (1));
  1864. }
  1865. void LookAndFeel_V2::drawTableHeaderColumn (Graphics& g, TableHeaderComponent& header,
  1866. const String& columnName, int /*columnId*/,
  1867. int width, int height, bool isMouseOver, bool isMouseDown,
  1868. int columnFlags)
  1869. {
  1870. auto highlightColour = header.findColour (TableHeaderComponent::highlightColourId);
  1871. if (isMouseDown)
  1872. g.fillAll (highlightColour);
  1873. else if (isMouseOver)
  1874. g.fillAll (highlightColour.withMultipliedAlpha (0.625f));
  1875. Rectangle<int> area (width, height);
  1876. area.reduce (4, 0);
  1877. if ((columnFlags & (TableHeaderComponent::sortedForwards | TableHeaderComponent::sortedBackwards)) != 0)
  1878. {
  1879. Path sortArrow;
  1880. sortArrow.addTriangle (0.0f, 0.0f,
  1881. 0.5f, (columnFlags & TableHeaderComponent::sortedForwards) != 0 ? -0.8f : 0.8f,
  1882. 1.0f, 0.0f);
  1883. g.setColour (Colour (0x99000000));
  1884. g.fillPath (sortArrow, sortArrow.getTransformToScaleToFit (area.removeFromRight (height / 2).reduced (2).toFloat(), true));
  1885. }
  1886. g.setColour (header.findColour (TableHeaderComponent::textColourId));
  1887. g.setFont (Font (height * 0.5f, Font::bold));
  1888. g.drawFittedText (columnName, area, Justification::centredLeft, 1);
  1889. }
  1890. //==============================================================================
  1891. void LookAndFeel_V2::drawLasso (Graphics& g, Component& lassoComp)
  1892. {
  1893. const int outlineThickness = 1;
  1894. g.fillAll (lassoComp.findColour (0x1000440 /*lassoFillColourId*/));
  1895. g.setColour (lassoComp.findColour (0x1000441 /*lassoOutlineColourId*/));
  1896. g.drawRect (lassoComp.getLocalBounds(), outlineThickness);
  1897. }
  1898. //==============================================================================
  1899. void LookAndFeel_V2::paintToolbarBackground (Graphics& g, int w, int h, Toolbar& toolbar)
  1900. {
  1901. auto background = toolbar.findColour (Toolbar::backgroundColourId);
  1902. g.setGradientFill (ColourGradient (background, 0.0f, 0.0f,
  1903. background.darker (0.1f),
  1904. toolbar.isVertical() ? w - 1.0f : 0.0f,
  1905. toolbar.isVertical() ? 0.0f : h - 1.0f,
  1906. false));
  1907. g.fillAll();
  1908. }
  1909. Button* LookAndFeel_V2::createToolbarMissingItemsButton (Toolbar& /*toolbar*/)
  1910. {
  1911. return createTabBarExtrasButton();
  1912. }
  1913. void LookAndFeel_V2::paintToolbarButtonBackground (Graphics& g, int /*width*/, int /*height*/,
  1914. bool isMouseOver, bool isMouseDown,
  1915. ToolbarItemComponent& component)
  1916. {
  1917. if (isMouseDown)
  1918. g.fillAll (component.findColour (Toolbar::buttonMouseDownBackgroundColourId, true));
  1919. else if (isMouseOver)
  1920. g.fillAll (component.findColour (Toolbar::buttonMouseOverBackgroundColourId, true));
  1921. }
  1922. void LookAndFeel_V2::paintToolbarButtonLabel (Graphics& g, int x, int y, int width, int height,
  1923. const String& text, ToolbarItemComponent& component)
  1924. {
  1925. g.setColour (component.findColour (Toolbar::labelTextColourId, true)
  1926. .withAlpha (component.isEnabled() ? 1.0f : 0.25f));
  1927. auto fontHeight = jmin (14.0f, height * 0.85f);
  1928. g.setFont (fontHeight);
  1929. g.drawFittedText (text,
  1930. x, y, width, height,
  1931. Justification::centred,
  1932. jmax (1, height / (int) fontHeight));
  1933. }
  1934. //==============================================================================
  1935. void LookAndFeel_V2::drawPropertyPanelSectionHeader (Graphics& g, const String& name,
  1936. bool isOpen, int width, int height)
  1937. {
  1938. auto buttonSize = height * 0.75f;
  1939. auto buttonIndent = (height - buttonSize) * 0.5f;
  1940. drawTreeviewPlusMinusBox (g, Rectangle<float> (buttonIndent, buttonIndent, buttonSize, buttonSize), Colours::white, isOpen, false);
  1941. auto textX = (int) (buttonIndent * 2.0f + buttonSize + 2.0f);
  1942. g.setColour (Colours::black);
  1943. g.setFont (Font (height * 0.7f, Font::bold));
  1944. g.drawText (name, textX, 0, width - textX - 4, height, Justification::centredLeft, true);
  1945. }
  1946. void LookAndFeel_V2::drawPropertyComponentBackground (Graphics& g, int width, int height, PropertyComponent& component)
  1947. {
  1948. g.setColour (component.findColour (PropertyComponent::backgroundColourId));
  1949. g.fillRect (0, 0, width, height - 1);
  1950. }
  1951. void LookAndFeel_V2::drawPropertyComponentLabel (Graphics& g, int, int height, PropertyComponent& component)
  1952. {
  1953. g.setColour (component.findColour (PropertyComponent::labelTextColourId)
  1954. .withMultipliedAlpha (component.isEnabled() ? 1.0f : 0.6f));
  1955. g.setFont (jmin (height, 24) * 0.65f);
  1956. auto r = getPropertyComponentContentPosition (component);
  1957. g.drawFittedText (component.getName(),
  1958. 3, r.getY(), r.getX() - 5, r.getHeight(),
  1959. Justification::centredLeft, 2);
  1960. }
  1961. Rectangle<int> LookAndFeel_V2::getPropertyComponentContentPosition (PropertyComponent& component)
  1962. {
  1963. const int textW = jmin (200, component.getWidth() / 3);
  1964. return Rectangle<int> (textW, 1, component.getWidth() - textW - 1, component.getHeight() - 3);
  1965. }
  1966. int LookAndFeel_V2::getPropertyPanelSectionHeaderHeight (const String& sectionTitle)
  1967. {
  1968. return sectionTitle.isEmpty() ? 0 : 22;
  1969. }
  1970. //==============================================================================
  1971. void LookAndFeel_V2::drawCallOutBoxBackground (CallOutBox& box, Graphics& g,
  1972. const Path& path, Image& cachedImage)
  1973. {
  1974. if (cachedImage.isNull())
  1975. {
  1976. cachedImage = Image (Image::ARGB, box.getWidth(), box.getHeight(), true);
  1977. Graphics g2 (cachedImage);
  1978. DropShadow (Colours::black.withAlpha (0.7f), 8, Point<int> (0, 2)).drawForPath (g2, path);
  1979. }
  1980. g.setColour (Colours::black);
  1981. g.drawImageAt (cachedImage, 0, 0);
  1982. g.setColour (Colour::greyLevel (0.23f).withAlpha (0.9f));
  1983. g.fillPath (path);
  1984. g.setColour (Colours::white.withAlpha (0.8f));
  1985. g.strokePath (path, PathStrokeType (2.0f));
  1986. }
  1987. int LookAndFeel_V2::getCallOutBoxBorderSize (const CallOutBox&)
  1988. {
  1989. return 20;
  1990. }
  1991. //==============================================================================
  1992. AttributedString LookAndFeel_V2::createFileChooserHeaderText (const String& title,
  1993. const String& instructions)
  1994. {
  1995. AttributedString s;
  1996. s.setJustification (Justification::centred);
  1997. auto colour = findColour (FileChooserDialogBox::titleTextColourId);
  1998. s.append (title + "\n\n", Font (17.0f, Font::bold), colour);
  1999. s.append (instructions, Font (14.0f), colour);
  2000. return s;
  2001. }
  2002. void LookAndFeel_V2::drawFileBrowserRow (Graphics& g, int width, int height,
  2003. const File&, const String& filename, Image* icon,
  2004. const String& fileSizeDescription,
  2005. const String& fileTimeDescription,
  2006. bool isDirectory, bool isItemSelected,
  2007. int /*itemIndex*/, DirectoryContentsDisplayComponent& dcc)
  2008. {
  2009. auto fileListComp = dynamic_cast<Component*> (&dcc);
  2010. if (isItemSelected)
  2011. g.fillAll (fileListComp != nullptr ? fileListComp->findColour (DirectoryContentsDisplayComponent::highlightColourId)
  2012. : findColour (DirectoryContentsDisplayComponent::highlightColourId));
  2013. const int x = 32;
  2014. g.setColour (Colours::black);
  2015. if (icon != nullptr && icon->isValid())
  2016. {
  2017. g.drawImageWithin (*icon, 2, 2, x - 4, height - 4,
  2018. RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize,
  2019. false);
  2020. }
  2021. else
  2022. {
  2023. if (auto* d = isDirectory ? getDefaultFolderImage()
  2024. : getDefaultDocumentFileImage())
  2025. d->drawWithin (g, Rectangle<float> (2.0f, 2.0f, x - 4.0f, height - 4.0f),
  2026. RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, 1.0f);
  2027. }
  2028. if (isItemSelected)
  2029. g.setColour (fileListComp != nullptr ? fileListComp->findColour (DirectoryContentsDisplayComponent::highlightedTextColourId)
  2030. : findColour (DirectoryContentsDisplayComponent::highlightedTextColourId));
  2031. else
  2032. g.setColour (fileListComp != nullptr ? fileListComp->findColour (DirectoryContentsDisplayComponent::textColourId)
  2033. : findColour (DirectoryContentsDisplayComponent::textColourId));
  2034. g.setFont (height * 0.7f);
  2035. if (width > 450 && ! isDirectory)
  2036. {
  2037. auto sizeX = roundToInt (width * 0.7f);
  2038. auto dateX = roundToInt (width * 0.8f);
  2039. g.drawFittedText (filename,
  2040. x, 0, sizeX - x, height,
  2041. Justification::centredLeft, 1);
  2042. g.setFont (height * 0.5f);
  2043. g.setColour (Colours::darkgrey);
  2044. if (! isDirectory)
  2045. {
  2046. g.drawFittedText (fileSizeDescription,
  2047. sizeX, 0, dateX - sizeX - 8, height,
  2048. Justification::centredRight, 1);
  2049. g.drawFittedText (fileTimeDescription,
  2050. dateX, 0, width - 8 - dateX, height,
  2051. Justification::centredRight, 1);
  2052. }
  2053. }
  2054. else
  2055. {
  2056. g.drawFittedText (filename,
  2057. x, 0, width - x, height,
  2058. Justification::centredLeft, 1);
  2059. }
  2060. }
  2061. Button* LookAndFeel_V2::createFileBrowserGoUpButton()
  2062. {
  2063. auto goUpButton = new DrawableButton ("up", DrawableButton::ImageOnButtonBackground);
  2064. Path arrowPath;
  2065. arrowPath.addArrow ({ 50.0f, 100.0f, 50.0f, 0.0f }, 40.0f, 100.0f, 50.0f);
  2066. DrawablePath arrowImage;
  2067. arrowImage.setFill (Colours::black.withAlpha (0.4f));
  2068. arrowImage.setPath (arrowPath);
  2069. goUpButton->setImages (&arrowImage);
  2070. return goUpButton;
  2071. }
  2072. void LookAndFeel_V2::layoutFileBrowserComponent (FileBrowserComponent& browserComp,
  2073. DirectoryContentsDisplayComponent* fileListComponent,
  2074. FilePreviewComponent* previewComp,
  2075. ComboBox* currentPathBox,
  2076. TextEditor* filenameBox,
  2077. Button* goUpButton)
  2078. {
  2079. const int x = 8;
  2080. auto w = browserComp.getWidth() - x - x;
  2081. if (previewComp != nullptr)
  2082. {
  2083. auto previewWidth = w / 3;
  2084. previewComp->setBounds (x + w - previewWidth, 0, previewWidth, browserComp.getHeight());
  2085. w -= previewWidth + 4;
  2086. }
  2087. int y = 4;
  2088. const int controlsHeight = 22;
  2089. const int upButtonWidth = 50;
  2090. auto bottomSectionHeight = controlsHeight + 8;
  2091. currentPathBox->setBounds (x, y, w - upButtonWidth - 6, controlsHeight);
  2092. goUpButton->setBounds (x + w - upButtonWidth, y, upButtonWidth, controlsHeight);
  2093. y += controlsHeight + 4;
  2094. if (auto listAsComp = dynamic_cast<Component*> (fileListComponent))
  2095. {
  2096. listAsComp->setBounds (x, y, w, browserComp.getHeight() - y - bottomSectionHeight);
  2097. y = listAsComp->getBottom() + 4;
  2098. }
  2099. filenameBox->setBounds (x + 50, y, w - 50, controlsHeight);
  2100. }
  2101. //==============================================================================
  2102. static Drawable* createDrawableFromSVG (const char* data)
  2103. {
  2104. std::unique_ptr<XmlElement> xml (XmlDocument::parse (data));
  2105. jassert (xml != nullptr);
  2106. return Drawable::createFromSVG (*xml);
  2107. }
  2108. const Drawable* LookAndFeel_V2::getDefaultFolderImage()
  2109. {
  2110. if (folderImage == nullptr)
  2111. folderImage.reset (createDrawableFromSVG (R"svgdata(
  2112. <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="706" height="532">
  2113. <defs>
  2114. <linearGradient id="a">
  2115. <stop stop-color="#adf" offset="0"/>
  2116. <stop stop-color="#ecfaff" offset="1"/>
  2117. </linearGradient>
  2118. <linearGradient id="b" x1=".6" x2="0" y1=".9" xlink:href="#a"/>
  2119. <linearGradient id="c" x1=".6" x2=".1" y1=".9" y2=".3" xlink:href="#a"/>
  2120. </defs>
  2121. <g class="currentLayer">
  2122. <path d="M112.1 104c-8.2 2.2-13.2 11.6-11.3 21l68.3 342.7c1.9 9.4 10.1 15.2 18.4 13l384.3-104.1c8.2-2.2 13.2-11.6 11.3-21l-48-266a15.8 15.8 0 0 0-18.4-12.8l-224.2 38s-20.3-41.3-28.3-39.3z" display="block" fill="url(#b)" stroke="#446c98" stroke-width="7"/>
  2123. <path d="M608.6 136.8L235.2 208a22.7 22.7 0 0 0-16 19l-40.8 241c1.7 8.4 9.6 14.5 17.8 12.3l380-104c8-2.2 10.7-10.2 12.3-18.4l38-210.1c.4-15.4-10.4-11.8-18-11.1z" display="block" fill="url(#c)" opacity=".8" stroke="#446c98" stroke-width="7"/>
  2124. </g>
  2125. </svg>
  2126. )svgdata"));
  2127. return folderImage.get();
  2128. }
  2129. const Drawable* LookAndFeel_V2::getDefaultDocumentFileImage()
  2130. {
  2131. if (documentImage == nullptr)
  2132. documentImage.reset (createDrawableFromSVG (R"svgdata(
  2133. <svg version="1" viewBox="-10 -10 450 600" xmlns="http://www.w3.org/2000/svg">
  2134. <path d="M17 0h290l120 132v426c0 10-8 19-17 19H17c-9 0-17-9-17-19V19C0 8 8 0 17 0z" fill="#e5e5e5" stroke="#888888" stroke-width="7"/>
  2135. <path d="M427 132H324c-9 0-17-9-17-19V0l120 132z" fill="#ccc"/>
  2136. </svg>
  2137. )svgdata"));
  2138. return documentImage.get();
  2139. }
  2140. //==============================================================================
  2141. static Path createPathFromData (float height, const unsigned char* data, size_t size)
  2142. {
  2143. Path p;
  2144. p.loadPathFromData (data, size);
  2145. p.scaleToFit (0, 0, height * 2.0f, height, true);
  2146. return p;
  2147. }
  2148. Path LookAndFeel_V2::getTickShape (float height)
  2149. {
  2150. static const unsigned char data[] =
  2151. {
  2152. 109,0,224,168,68,0,0,119,67,108,0,224,172,68,0,128,146,67,113,0,192,148,68,0,0,219,67,0,96,110,68,0,224,56,68,113,0,64,51,68,0,32,130,68,0,64,20,68,0,224,
  2153. 162,68,108,0,128,3,68,0,128,168,68,113,0,128,221,67,0,192,175,68,0,0,207,67,0,32,179,68,113,0,0,201,67,0,224,173,68,0,0,181,67,0,224,161,68,108,0,128,168,67,
  2154. 0,128,154,68,113,0,128,141,67,0,192,138,68,0,128,108,67,0,64,131,68,113,0,0,62,67,0,128,119,68,0,0,5,67,0,128,114,68,113,0,0,102,67,0,192,88,68,0,128,155,
  2155. 67,0,192,88,68,113,0,0,190,67,0,192,88,68,0,128,232,67,0,224,131,68,108,0,128,246,67,0,192,139,68,113,0,64,33,68,0,128,87,68,0,0,93,68,0,224,26,68,113,0,
  2156. 96,140,68,0,128,188,67,0,224,168,68,0,0,119,67,99,101
  2157. };
  2158. return createPathFromData (height, data, sizeof (data));
  2159. }
  2160. Path LookAndFeel_V2::getCrossShape (float height)
  2161. {
  2162. static const unsigned char data[] =
  2163. {
  2164. 109,0,0,17,68,0,96,145,68,108,0,192,13,68,0,192,147,68,113,0,0,213,67,0,64,174,68,0,0,168,67,0,64,174,68,113,0,0,104,67,0,64,174,68,0,0,5,67,0,64,
  2165. 153,68,113,0,0,18,67,0,64,153,68,0,0,24,67,0,64,153,68,113,0,0,135,67,0,64,153,68,0,128,207,67,0,224,130,68,108,0,0,220,67,0,0,126,68,108,0,0,204,67,
  2166. 0,128,117,68,113,0,0,138,67,0,64,82,68,0,0,138,67,0,192,57,68,113,0,0,138,67,0,192,37,68,0,128,210,67,0,64,10,68,113,0,128,220,67,0,64,45,68,0,0,8,
  2167. 68,0,128,78,68,108,0,192,14,68,0,0,87,68,108,0,64,20,68,0,0,80,68,113,0,192,57,68,0,0,32,68,0,128,88,68,0,0,32,68,113,0,64,112,68,0,0,32,68,0,
  2168. 128,124,68,0,64,68,68,113,0,0,121,68,0,192,67,68,0,128,119,68,0,192,67,68,113,0,192,108,68,0,192,67,68,0,32,89,68,0,96,82,68,113,0,128,69,68,0,0,97,68,
  2169. 0,0,56,68,0,64,115,68,108,0,64,49,68,0,128,124,68,108,0,192,55,68,0,96,129,68,113,0,0,92,68,0,224,146,68,0,192,129,68,0,224,146,68,113,0,64,110,68,0,64,
  2170. 168,68,0,64,87,68,0,64,168,68,113,0,128,66,68,0,64,168,68,0,64,27,68,0,32,150,68,99,101
  2171. };
  2172. return createPathFromData (height, data, sizeof (data));
  2173. }
  2174. //==============================================================================
  2175. void LookAndFeel_V2::drawLevelMeter (Graphics& g, int width, int height, float level)
  2176. {
  2177. g.setColour (Colours::white.withAlpha (0.7f));
  2178. g.fillRoundedRectangle (0.0f, 0.0f, (float) width, (float) height, 3.0f);
  2179. g.setColour (Colours::black.withAlpha (0.2f));
  2180. g.drawRoundedRectangle (1.0f, 1.0f, width - 2.0f, height - 2.0f, 3.0f, 1.0f);
  2181. const int totalBlocks = 7;
  2182. const int numBlocks = roundToInt (totalBlocks * level);
  2183. auto w = (width - 6.0f) / (float) totalBlocks;
  2184. for (int i = 0; i < totalBlocks; ++i)
  2185. {
  2186. if (i >= numBlocks)
  2187. g.setColour (Colours::lightblue.withAlpha (0.6f));
  2188. else
  2189. g.setColour (i < totalBlocks - 1 ? Colours::blue.withAlpha (0.5f)
  2190. : Colours::red);
  2191. g.fillRoundedRectangle (3.0f + i * w + w * 0.1f, 3.0f, w * 0.8f, height - 6.0f, w * 0.4f);
  2192. }
  2193. }
  2194. //==============================================================================
  2195. void LookAndFeel_V2::drawKeymapChangeButton (Graphics& g, int width, int height, Button& button, const String& keyDescription)
  2196. {
  2197. auto textColour = button.findColour (0x100ad01 /*KeyMappingEditorComponent::textColourId*/, true);
  2198. if (keyDescription.isNotEmpty())
  2199. {
  2200. if (button.isEnabled())
  2201. {
  2202. auto alpha = button.isDown() ? 0.3f : (button.isOver() ? 0.15f : 0.08f);
  2203. g.fillAll (textColour.withAlpha (alpha));
  2204. g.setOpacity (0.3f);
  2205. drawBevel (g, 0, 0, width, height, 2);
  2206. }
  2207. g.setColour (textColour);
  2208. g.setFont (height * 0.6f);
  2209. g.drawFittedText (keyDescription,
  2210. 3, 0, width - 6, height,
  2211. Justification::centred, 1);
  2212. }
  2213. else
  2214. {
  2215. const float thickness = 7.0f;
  2216. const float indent = 22.0f;
  2217. Path p;
  2218. p.addEllipse (0.0f, 0.0f, 100.0f, 100.0f);
  2219. p.addRectangle (indent, 50.0f - thickness, 100.0f - indent * 2.0f, thickness * 2.0f);
  2220. p.addRectangle (50.0f - thickness, indent, thickness * 2.0f, 50.0f - indent - thickness);
  2221. p.addRectangle (50.0f - thickness, 50.0f + thickness, thickness * 2.0f, 50.0f - indent - thickness);
  2222. p.setUsingNonZeroWinding (false);
  2223. g.setColour (textColour.withAlpha (button.isDown() ? 0.7f : (button.isOver() ? 0.5f : 0.3f)));
  2224. g.fillPath (p, p.getTransformToScaleToFit (2.0f, 2.0f, width - 4.0f, height - 4.0f, true));
  2225. }
  2226. if (button.hasKeyboardFocus (false))
  2227. {
  2228. g.setColour (textColour.withAlpha (0.4f));
  2229. g.drawRect (0, 0, width, height);
  2230. }
  2231. }
  2232. //==============================================================================
  2233. Font LookAndFeel_V2::getSidePanelTitleFont (SidePanel&)
  2234. {
  2235. return Font (18.0f);
  2236. }
  2237. Justification LookAndFeel_V2::getSidePanelTitleJustification (SidePanel& panel)
  2238. {
  2239. return panel.isPanelOnLeft() ? Justification::centredRight
  2240. : Justification::centredLeft;
  2241. }
  2242. Path LookAndFeel_V2::getSidePanelDismissButtonShape (SidePanel& panel)
  2243. {
  2244. return getCrossShape ((float) panel.getTitleBarHeight());
  2245. }
  2246. //==============================================================================
  2247. void LookAndFeel_V2::drawBevel (Graphics& g, const int x, const int y, const int width, const int height,
  2248. const int bevelThickness, const Colour& topLeftColour, const Colour& bottomRightColour,
  2249. const bool useGradient, const bool sharpEdgeOnOutside)
  2250. {
  2251. if (g.clipRegionIntersects (Rectangle<int> (x, y, width, height)))
  2252. {
  2253. auto& context = g.getInternalContext();
  2254. context.saveState();
  2255. for (int i = bevelThickness; --i >= 0;)
  2256. {
  2257. const float op = useGradient ? (sharpEdgeOnOutside ? bevelThickness - i : i) / (float) bevelThickness
  2258. : 1.0f;
  2259. context.setFill (topLeftColour.withMultipliedAlpha (op));
  2260. context.fillRect (Rectangle<int> (x + i, y + i, width - i * 2, 1), false);
  2261. context.setFill (topLeftColour.withMultipliedAlpha (op * 0.75f));
  2262. context.fillRect (Rectangle<int> (x + i, y + i + 1, 1, height - i * 2 - 2), false);
  2263. context.setFill (bottomRightColour.withMultipliedAlpha (op));
  2264. context.fillRect (Rectangle<int> (x + i, y + height - i - 1, width - i * 2, 1), false);
  2265. context.setFill (bottomRightColour.withMultipliedAlpha (op * 0.75f));
  2266. context.fillRect (Rectangle<int> (x + width - i - 1, y + i + 1, 1, height - i * 2 - 2), false);
  2267. }
  2268. context.restoreState();
  2269. }
  2270. }
  2271. //==============================================================================
  2272. void LookAndFeel_V2::drawShinyButtonShape (Graphics& g, float x, float y, float w, float h,
  2273. float maxCornerSize, const Colour& baseColour, float strokeWidth,
  2274. bool flatOnLeft, bool flatOnRight, bool flatOnTop, bool flatOnBottom) noexcept
  2275. {
  2276. if (w <= strokeWidth * 1.1f || h <= strokeWidth * 1.1f)
  2277. return;
  2278. auto cs = jmin (maxCornerSize, w * 0.5f, h * 0.5f);
  2279. Path outline;
  2280. outline.addRoundedRectangle (x, y, w, h, cs, cs,
  2281. ! (flatOnLeft || flatOnTop),
  2282. ! (flatOnRight || flatOnTop),
  2283. ! (flatOnLeft || flatOnBottom),
  2284. ! (flatOnRight || flatOnBottom));
  2285. ColourGradient cg (baseColour, 0.0f, y,
  2286. baseColour.overlaidWith (Colour (0x070000ff)), 0.0f, y + h,
  2287. false);
  2288. cg.addColour (0.5, baseColour.overlaidWith (Colour (0x33ffffff)));
  2289. cg.addColour (0.51, baseColour.overlaidWith (Colour (0x110000ff)));
  2290. g.setGradientFill (cg);
  2291. g.fillPath (outline);
  2292. g.setColour (Colour (0x80000000));
  2293. g.strokePath (outline, PathStrokeType (strokeWidth));
  2294. }
  2295. //==============================================================================
  2296. void LookAndFeel_V2::drawGlassSphere (Graphics& g, const float x, const float y,
  2297. const float diameter, const Colour& colour,
  2298. const float outlineThickness) noexcept
  2299. {
  2300. if (diameter <= outlineThickness)
  2301. return;
  2302. Path p;
  2303. p.addEllipse (x, y, diameter, diameter);
  2304. {
  2305. ColourGradient cg (Colours::white.overlaidWith (colour.withMultipliedAlpha (0.3f)), 0, y,
  2306. Colours::white.overlaidWith (colour.withMultipliedAlpha (0.3f)), 0, y + diameter, false);
  2307. cg.addColour (0.4, Colours::white.overlaidWith (colour));
  2308. g.setGradientFill (cg);
  2309. g.fillPath (p);
  2310. }
  2311. g.setGradientFill (ColourGradient (Colours::white, 0, y + diameter * 0.06f,
  2312. Colours::transparentWhite, 0, y + diameter * 0.3f, false));
  2313. g.fillEllipse (x + diameter * 0.2f, y + diameter * 0.05f, diameter * 0.6f, diameter * 0.4f);
  2314. ColourGradient cg (Colours::transparentBlack,
  2315. x + diameter * 0.5f, y + diameter * 0.5f,
  2316. Colours::black.withAlpha (0.5f * outlineThickness * colour.getFloatAlpha()),
  2317. x, y + diameter * 0.5f, true);
  2318. cg.addColour (0.7, Colours::transparentBlack);
  2319. cg.addColour (0.8, Colours::black.withAlpha (0.1f * outlineThickness));
  2320. g.setGradientFill (cg);
  2321. g.fillPath (p);
  2322. g.setColour (Colours::black.withAlpha (0.5f * colour.getFloatAlpha()));
  2323. g.drawEllipse (x, y, diameter, diameter, outlineThickness);
  2324. }
  2325. //==============================================================================
  2326. void LookAndFeel_V2::drawGlassPointer (Graphics& g,
  2327. const float x, const float y, const float diameter,
  2328. const Colour& colour, const float outlineThickness,
  2329. const int direction) noexcept
  2330. {
  2331. if (diameter <= outlineThickness)
  2332. return;
  2333. Path p;
  2334. p.startNewSubPath (x + diameter * 0.5f, y);
  2335. p.lineTo (x + diameter, y + diameter * 0.6f);
  2336. p.lineTo (x + diameter, y + diameter);
  2337. p.lineTo (x, y + diameter);
  2338. p.lineTo (x, y + diameter * 0.6f);
  2339. p.closeSubPath();
  2340. p.applyTransform (AffineTransform::rotation (direction * MathConstants<float>::halfPi, x + diameter * 0.5f, y + diameter * 0.5f));
  2341. {
  2342. ColourGradient cg (Colours::white.overlaidWith (colour.withMultipliedAlpha (0.3f)), 0, y,
  2343. Colours::white.overlaidWith (colour.withMultipliedAlpha (0.3f)), 0, y + diameter, false);
  2344. cg.addColour (0.4, Colours::white.overlaidWith (colour));
  2345. g.setGradientFill (cg);
  2346. g.fillPath (p);
  2347. }
  2348. ColourGradient cg (Colours::transparentBlack,
  2349. x + diameter * 0.5f, y + diameter * 0.5f,
  2350. Colours::black.withAlpha (0.5f * outlineThickness * colour.getFloatAlpha()),
  2351. x - diameter * 0.2f, y + diameter * 0.5f, true);
  2352. cg.addColour (0.5, Colours::transparentBlack);
  2353. cg.addColour (0.7, Colours::black.withAlpha (0.07f * outlineThickness));
  2354. g.setGradientFill (cg);
  2355. g.fillPath (p);
  2356. g.setColour (Colours::black.withAlpha (0.5f * colour.getFloatAlpha()));
  2357. g.strokePath (p, PathStrokeType (outlineThickness));
  2358. }
  2359. //==============================================================================
  2360. void LookAndFeel_V2::drawGlassLozenge (Graphics& g,
  2361. float x, float y, float width, float height,
  2362. const Colour& colour, float outlineThickness, float cornerSize,
  2363. bool flatOnLeft, bool flatOnRight, bool flatOnTop, bool flatOnBottom) noexcept
  2364. {
  2365. if (width <= outlineThickness || height <= outlineThickness)
  2366. return;
  2367. auto intX = (int) x;
  2368. auto intY = (int) y;
  2369. auto intW = (int) width;
  2370. auto intH = (int) height;
  2371. auto cs = cornerSize < 0 ? jmin (width * 0.5f, height * 0.5f) : cornerSize;
  2372. auto edgeBlurRadius = height * 0.75f + (height - cs * 2.0f);
  2373. auto intEdge = (int) edgeBlurRadius;
  2374. Path outline;
  2375. outline.addRoundedRectangle (x, y, width, height, cs, cs,
  2376. ! (flatOnLeft || flatOnTop),
  2377. ! (flatOnRight || flatOnTop),
  2378. ! (flatOnLeft || flatOnBottom),
  2379. ! (flatOnRight || flatOnBottom));
  2380. {
  2381. ColourGradient cg (colour.darker (0.2f), 0, y,
  2382. colour.darker (0.2f), 0, y + height, false);
  2383. cg.addColour (0.03, colour.withMultipliedAlpha (0.3f));
  2384. cg.addColour (0.4, colour);
  2385. cg.addColour (0.97, colour.withMultipliedAlpha (0.3f));
  2386. g.setGradientFill (cg);
  2387. g.fillPath (outline);
  2388. }
  2389. ColourGradient cg (Colours::transparentBlack, x + edgeBlurRadius, y + height * 0.5f,
  2390. colour.darker (0.2f), x, y + height * 0.5f, true);
  2391. cg.addColour (jlimit (0.0, 1.0, 1.0 - (cs * 0.5f) / edgeBlurRadius), Colours::transparentBlack);
  2392. cg.addColour (jlimit (0.0, 1.0, 1.0 - (cs * 0.25f) / edgeBlurRadius), colour.darker (0.2f).withMultipliedAlpha (0.3f));
  2393. if (! (flatOnLeft || flatOnTop || flatOnBottom))
  2394. {
  2395. g.saveState();
  2396. g.setGradientFill (cg);
  2397. g.reduceClipRegion (intX, intY, intEdge, intH);
  2398. g.fillPath (outline);
  2399. g.restoreState();
  2400. }
  2401. if (! (flatOnRight || flatOnTop || flatOnBottom))
  2402. {
  2403. cg.point1.setX (x + width - edgeBlurRadius);
  2404. cg.point2.setX (x + width);
  2405. g.saveState();
  2406. g.setGradientFill (cg);
  2407. g.reduceClipRegion (intX + intW - intEdge, intY, 2 + intEdge, intH);
  2408. g.fillPath (outline);
  2409. g.restoreState();
  2410. }
  2411. {
  2412. auto leftIndent = (flatOnTop || flatOnLeft) ? 0.0f : cs * 0.4f;
  2413. auto rightIndent = (flatOnTop || flatOnRight) ? 0.0f : cs * 0.4f;
  2414. Path highlight;
  2415. highlight.addRoundedRectangle (x + leftIndent,
  2416. y + cs * 0.1f,
  2417. width - (leftIndent + rightIndent),
  2418. height * 0.4f,
  2419. cs * 0.4f,
  2420. cs * 0.4f,
  2421. ! (flatOnLeft || flatOnTop),
  2422. ! (flatOnRight || flatOnTop),
  2423. ! (flatOnLeft || flatOnBottom),
  2424. ! (flatOnRight || flatOnBottom));
  2425. g.setGradientFill (ColourGradient (colour.brighter (10.0f), 0, y + height * 0.06f,
  2426. Colours::transparentWhite, 0, y + height * 0.4f, false));
  2427. g.fillPath (highlight);
  2428. }
  2429. g.setColour (colour.darker().withMultipliedAlpha (1.5f));
  2430. g.strokePath (outline, PathStrokeType (outlineThickness));
  2431. }
  2432. } // namespace juce