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.

405 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #include "jucer_Application.h"
  18. #include "jucer_AppearanceSettings.h"
  19. #include "jucer_GlobalPreferences.h"
  20. namespace AppearanceColours
  21. {
  22. struct ColourInfo
  23. {
  24. const char* name;
  25. int colourID;
  26. bool mustBeOpaque;
  27. bool applyToEditorOnly;
  28. };
  29. static const ColourInfo colours[] =
  30. {
  31. { "Main Window Bkgd", mainBackgroundColourId, true, false },
  32. { "Treeview Highlight", TreeView::selectedItemBackgroundColourId, false, false },
  33. { "Code Background", CodeEditorComponent::backgroundColourId, true, false },
  34. { "Line Number Bkgd", CodeEditorComponent::lineNumberBackgroundId, false, false },
  35. { "Line Numbers", CodeEditorComponent::lineNumberTextId, false, false },
  36. { "Plain Text", CodeEditorComponent::defaultTextColourId, false, false },
  37. { "Selected Text Bkgd", CodeEditorComponent::highlightColourId, false, false },
  38. { "Caret", CaretComponent::caretColourId, false, true }
  39. };
  40. enum
  41. {
  42. numColours = sizeof (AppearanceColours::colours) / sizeof (AppearanceColours::colours[0])
  43. };
  44. }
  45. //==============================================================================
  46. AppearanceSettings::AppearanceSettings (bool updateAppWhenChanged)
  47. : settings ("COLOUR_SCHEME")
  48. {
  49. if (! IntrojucerApp::getApp().isRunningCommandLine)
  50. {
  51. IntrojucerLookAndFeel lf;
  52. for (int i = 0; i < AppearanceColours::numColours; ++i)
  53. getColourValue (AppearanceColours::colours[i].name) = lf.findColour (AppearanceColours::colours[i].colourID).toString();
  54. CodeDocument doc;
  55. CPlusPlusCodeTokeniser tokeniser;
  56. CodeEditorComponent editor (doc, &tokeniser);
  57. const CodeEditorComponent::ColourScheme cs (editor.getColourScheme());
  58. for (int i = cs.types.size(); --i >= 0;)
  59. {
  60. CodeEditorComponent::ColourScheme::TokenType& t = cs.types.getReference(i);
  61. getColourValue (t.name) = t.colour.toString();
  62. }
  63. getCodeFontValue() = getDefaultCodeFont().toString();
  64. if (updateAppWhenChanged)
  65. settings.addListener (this);
  66. }
  67. }
  68. File AppearanceSettings::getSchemesFolder()
  69. {
  70. File f (getGlobalProperties().getFile().getSiblingFile ("Schemes"));
  71. f.createDirectory();
  72. return f;
  73. }
  74. void AppearanceSettings::writeDefaultSchemeFile (const String& xmlString, const String& name)
  75. {
  76. const File file (getSchemesFolder().getChildFile (name).withFileExtension (getSchemeFileSuffix()));
  77. AppearanceSettings settings (false);
  78. ScopedPointer<XmlElement> xml (XmlDocument::parse (xmlString));
  79. if (xml != nullptr)
  80. settings.readFromXML (*xml);
  81. settings.writeToFile (file);
  82. }
  83. void AppearanceSettings::refreshPresetSchemeList()
  84. {
  85. writeDefaultSchemeFile (BinaryData::colourscheme_dark_xml, "Default (Dark)");
  86. writeDefaultSchemeFile (BinaryData::colourscheme_light_xml, "Default (Light)");
  87. Array<File> newSchemes;
  88. getSchemesFolder().findChildFiles (newSchemes, File::findFiles, false, String ("*") + getSchemeFileSuffix());
  89. if (newSchemes != presetSchemeFiles)
  90. {
  91. presetSchemeFiles.swapWith (newSchemes);
  92. IntrojucerApp::getCommandManager().commandStatusChanged();
  93. }
  94. }
  95. StringArray AppearanceSettings::getPresetSchemes()
  96. {
  97. StringArray s;
  98. for (int i = 0; i < presetSchemeFiles.size(); ++i)
  99. s.add (presetSchemeFiles.getReference(i).getFileNameWithoutExtension());
  100. return s;
  101. }
  102. void AppearanceSettings::selectPresetScheme (int index)
  103. {
  104. readFromFile (presetSchemeFiles [index]);
  105. }
  106. bool AppearanceSettings::readFromXML (const XmlElement& xml)
  107. {
  108. if (xml.hasTagName (settings.getType().toString()))
  109. {
  110. const ValueTree newSettings (ValueTree::fromXml (xml));
  111. // we'll manually copy across the new properties to the existing tree so that
  112. // any open editors will be kept up to date..
  113. settings.copyPropertiesFrom (newSettings, nullptr);
  114. for (int i = settings.getNumChildren(); --i >= 0;)
  115. {
  116. ValueTree c (settings.getChild (i));
  117. const ValueTree newValue (newSettings.getChildWithProperty (Ids::name, c.getProperty (Ids::name)));
  118. if (newValue.isValid())
  119. c.copyPropertiesFrom (newValue, nullptr);
  120. }
  121. return true;
  122. }
  123. return false;
  124. }
  125. bool AppearanceSettings::readFromFile (const File& file)
  126. {
  127. const ScopedPointer<XmlElement> xml (XmlDocument::parse (file));
  128. return xml != nullptr && readFromXML (*xml);
  129. }
  130. bool AppearanceSettings::writeToFile (const File& file) const
  131. {
  132. const ScopedPointer<XmlElement> xml (settings.createXml());
  133. return xml != nullptr && xml->writeToFile (file, String::empty);
  134. }
  135. Font AppearanceSettings::getDefaultCodeFont()
  136. {
  137. return Font (Font::getDefaultMonospacedFontName(), Font::getDefaultStyle(), 13.0f);
  138. }
  139. StringArray AppearanceSettings::getColourNames() const
  140. {
  141. StringArray s;
  142. for (int i = 0; i < settings.getNumChildren(); ++i)
  143. {
  144. const ValueTree c (settings.getChild(i));
  145. if (c.hasType ("COLOUR"))
  146. s.add (c [Ids::name]);
  147. }
  148. return s;
  149. }
  150. void AppearanceSettings::updateColourScheme()
  151. {
  152. applyToLookAndFeel (LookAndFeel::getDefaultLookAndFeel());
  153. IntrojucerApp::getApp().mainWindowList.sendLookAndFeelChange();
  154. }
  155. void AppearanceSettings::applyToLookAndFeel (LookAndFeel& lf) const
  156. {
  157. for (int i = 0; i < AppearanceColours::numColours; ++i)
  158. {
  159. Colour col;
  160. if (getColour (AppearanceColours::colours[i].name, col))
  161. {
  162. if (AppearanceColours::colours[i].mustBeOpaque)
  163. col = Colours::white.overlaidWith (col);
  164. if (! AppearanceColours::colours[i].applyToEditorOnly)
  165. lf.setColour (AppearanceColours::colours[i].colourID, col);
  166. }
  167. }
  168. lf.setColour (ScrollBar::thumbColourId, lf.findColour (mainBackgroundColourId).contrasting().withAlpha (0.13f));
  169. }
  170. void AppearanceSettings::applyToCodeEditor (CodeEditorComponent& editor) const
  171. {
  172. CodeEditorComponent::ColourScheme cs (editor.getColourScheme());
  173. for (int i = cs.types.size(); --i >= 0;)
  174. {
  175. CodeEditorComponent::ColourScheme::TokenType& t = cs.types.getReference(i);
  176. getColour (t.name, t.colour);
  177. }
  178. editor.setColourScheme (cs);
  179. editor.setFont (getCodeFont());
  180. for (int i = 0; i < AppearanceColours::numColours; ++i)
  181. {
  182. if (AppearanceColours::colours[i].applyToEditorOnly)
  183. {
  184. Colour col;
  185. if (getColour (AppearanceColours::colours[i].name, col))
  186. editor.setColour (AppearanceColours::colours[i].colourID, col);
  187. }
  188. }
  189. editor.setColour (ScrollBar::thumbColourId, editor.findColour (CodeEditorComponent::backgroundColourId)
  190. .contrasting()
  191. .withAlpha (0.13f));
  192. }
  193. Font AppearanceSettings::getCodeFont() const
  194. {
  195. const String fontString (settings [Ids::font].toString());
  196. if (fontString.isEmpty())
  197. return getDefaultCodeFont();
  198. return Font::fromString (fontString);
  199. }
  200. Value AppearanceSettings::getCodeFontValue()
  201. {
  202. return settings.getPropertyAsValue (Ids::font, nullptr);
  203. }
  204. Value AppearanceSettings::getColourValue (const String& colourName)
  205. {
  206. ValueTree c (settings.getChildWithProperty (Ids::name, colourName));
  207. if (! c.isValid())
  208. {
  209. c = ValueTree ("COLOUR");
  210. c.setProperty (Ids::name, colourName, nullptr);
  211. settings.addChild (c, -1, nullptr);
  212. }
  213. return c.getPropertyAsValue (Ids::colour, nullptr);
  214. }
  215. bool AppearanceSettings::getColour (const String& name, Colour& result) const
  216. {
  217. const ValueTree colour (settings.getChildWithProperty (Ids::name, name));
  218. if (colour.isValid())
  219. {
  220. result = Colour::fromString (colour [Ids::colour].toString());
  221. return true;
  222. }
  223. return false;
  224. }
  225. //==============================================================================
  226. IntrojucerLookAndFeel::IntrojucerLookAndFeel()
  227. {
  228. setColour (mainBackgroundColourId, Colour::greyLevel (0.8f));
  229. }
  230. int IntrojucerLookAndFeel::getTabButtonBestWidth (TabBarButton&, int) { return 120; }
  231. Colour IntrojucerLookAndFeel::getTabBackgroundColour (TabBarButton& button)
  232. {
  233. const Colour bkg (button.findColour (mainBackgroundColourId).contrasting (0.15f));
  234. if (button.isFrontTab())
  235. return bkg.overlaidWith (Colours::yellow.withAlpha (0.5f));
  236. return bkg;
  237. }
  238. void IntrojucerLookAndFeel::drawTabButton (TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown)
  239. {
  240. const Rectangle<int> activeArea (button.getActiveArea());
  241. const Colour bkg (getTabBackgroundColour (button));
  242. g.setGradientFill (ColourGradient (bkg.brighter (0.1f), 0, (float) activeArea.getY(),
  243. bkg.darker (0.1f), 0, (float) activeArea.getBottom(), false));
  244. g.fillRect (activeArea);
  245. g.setColour (button.findColour (mainBackgroundColourId).darker (0.3f));
  246. g.drawRect (activeArea);
  247. const float alpha = button.isEnabled() ? ((isMouseOver || isMouseDown) ? 1.0f : 0.8f) : 0.3f;
  248. const Colour col (bkg.contrasting().withMultipliedAlpha (alpha));
  249. TextLayout textLayout;
  250. LookAndFeel_V3::createTabTextLayout (button, (float) activeArea.getWidth(), (float) activeArea.getHeight(), col, textLayout);
  251. textLayout.draw (g, button.getTextArea().toFloat());
  252. }
  253. void IntrojucerLookAndFeel::drawConcertinaPanelHeader (Graphics& g, const Rectangle<int>& area,
  254. bool isMouseOver, bool /*isMouseDown*/,
  255. ConcertinaPanel&, Component& panel)
  256. {
  257. const Colour bkg (Colours::grey);
  258. g.setGradientFill (ColourGradient (Colour::greyLevel (isMouseOver ? 0.6f : 0.5f), 0, (float) area.getY(),
  259. Colour::greyLevel (0.4f), 0, (float) area.getBottom(), false));
  260. g.fillAll();
  261. g.setColour (bkg.contrasting().withAlpha (0.1f));
  262. g.fillRect (area.withHeight (1));
  263. g.fillRect (area.withTop (area.getBottom() - 1));
  264. g.setColour (bkg.contrasting());
  265. g.setFont (Font (area.getHeight() * 0.6f).boldened());
  266. g.drawFittedText (panel.getName(), 4, 0, area.getWidth() - 6, area.getHeight(), Justification::centredLeft, 1);
  267. }
  268. static Range<float> getBrightnessRange (const Image& im)
  269. {
  270. float minB = 1.0f, maxB = 0;
  271. const int w = im.getWidth();
  272. const int h = im.getHeight();
  273. for (int y = 0; y < h; ++y)
  274. {
  275. for (int x = 0; x < w; ++x)
  276. {
  277. const float b = im.getPixelAt (x, y).getBrightness();
  278. minB = jmin (minB, b);
  279. maxB = jmax (maxB, b);
  280. }
  281. }
  282. return Range<float> (minB, maxB);
  283. }
  284. void IntrojucerLookAndFeel::fillWithBackgroundTexture (Graphics& g)
  285. {
  286. const Colour bkg (findColour (mainBackgroundColourId));
  287. if (backgroundTextureBaseColour != bkg)
  288. {
  289. backgroundTextureBaseColour = bkg;
  290. const Image original (ImageCache::getFromMemory (BinaryData::background_tile_png,
  291. BinaryData::background_tile_pngSize));
  292. const int w = original.getWidth();
  293. const int h = original.getHeight();
  294. backgroundTexture = Image (Image::RGB, w, h, false);
  295. const Range<float> brightnessRange (getBrightnessRange (original));
  296. const float brightnessOffset = (brightnessRange.getStart() + brightnessRange.getEnd()) / 2.0f;
  297. const float brightnessScale = 0.025f / brightnessRange.getLength();
  298. const float bkgB = bkg.getBrightness();
  299. for (int y = 0; y < h; ++y)
  300. {
  301. for (int x = 0; x < w; ++x)
  302. {
  303. const float b = (original.getPixelAt (x, y).getBrightness() - brightnessOffset) * brightnessScale;
  304. backgroundTexture.setPixelAt (x, y, bkg.withBrightness (jlimit (0.0f, 1.0f, bkgB + b)));
  305. }
  306. }
  307. }
  308. g.setTiledImageFill (backgroundTexture, 0, 0, 1.0f);
  309. g.fillAll();
  310. }
  311. void IntrojucerLookAndFeel::fillWithBackgroundTexture (Component& c, Graphics& g)
  312. {
  313. dynamic_cast<IntrojucerLookAndFeel&> (c.getLookAndFeel()).fillWithBackgroundTexture (g);
  314. }