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.

452 lines
15KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCETICE project - Copyright 2009 by Lucio Asnaghi.
  4. JUCETICE is based around the JUCE library - "Jules' Utility Class Extensions"
  5. Copyright 2007 by Julian Storer.
  6. ------------------------------------------------------------------------------
  7. JUCE and JUCETICE can be redistributed and/or modified under the terms of
  8. the GNU General Public License, as published by the Free Software Foundation;
  9. either version 2 of the License, or (at your option) any later version.
  10. JUCE and JUCETICE are distributed in the hope that they will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with JUCE and JUCETICE; if not, visit www.gnu.org/licenses or write to
  16. Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  17. Boston, MA 02111-1307 USA
  18. ==============================================================================
  19. @author Mike W. Smith
  20. @tweaker Lucio Asnaghi
  21. ==============================================================================
  22. */
  23. BEGIN_JUCE_NAMESPACE
  24. //==============================================================================
  25. MeterComponent::MeterComponent(
  26. int type,
  27. int segs,
  28. int markerWidth,
  29. const Colour& min,
  30. const Colour& thresh,
  31. const Colour& max,
  32. const Colour& back,
  33. float threshold) :
  34. Component("Meter Component"),
  35. m_img(0),
  36. m_value(0),
  37. m_skew(1.0f),
  38. m_threshold(threshold),
  39. m_meterType(type),
  40. m_segments(segs),
  41. m_markerWidth(markerWidth),
  42. m_inset(0),
  43. m_raised(false),
  44. m_minColour(min),
  45. m_thresholdColour(thresh),
  46. m_maxColour(max),
  47. m_backgroundColour(back),
  48. m_decayTime(50),
  49. m_decayPercent(0.707f),
  50. m_decayToValue(0),
  51. m_hold(4),
  52. m_monostable(0),
  53. m_background(0),
  54. m_overlay(0),
  55. m_minPosition(0.1f),
  56. m_maxPosition(0.9f),
  57. m_needleLength(0),
  58. m_needleWidth(markerWidth)
  59. {
  60. if(m_meterType == MeterAnalog)
  61. if(!m_segments)
  62. // Minimum analog meter wiper width
  63. m_segments = 8;
  64. startTimer(m_decayTime);
  65. }
  66. //==============================================================================
  67. MeterComponent::MeterComponent(Image background,
  68. Image overlay,
  69. float minPosition,
  70. float maxPosition,
  71. Point<int>& needleCenter,
  72. int needleLength,
  73. int needleWidth,
  74. int arrowLength,
  75. int arrowWidth,
  76. const Colour& needleColour,
  77. int needleDropShadow,
  78. int dropDistance) :
  79. Component("Meter Component"),
  80. m_img(0),
  81. m_value(0),
  82. m_skew(1.0f),
  83. m_threshold(0.707f),
  84. m_meterType(MeterAnalog),
  85. m_segments(0),
  86. m_markerWidth(0),
  87. m_inset(0),
  88. m_raised(false),
  89. m_minColour(0),
  90. m_thresholdColour(0),
  91. m_maxColour(0),
  92. m_backgroundColour(0),
  93. m_decayTime(50),
  94. m_decayPercent(0.707f),
  95. m_decayToValue(0),
  96. m_hold(4),
  97. m_monostable(0),
  98. m_background(0),
  99. m_overlay(0),
  100. m_minPosition(minPosition),
  101. m_maxPosition(maxPosition),
  102. m_needleLength(needleLength),
  103. m_needleWidth(needleWidth),
  104. m_arrowLength(arrowLength),
  105. m_arrowWidth(arrowWidth),
  106. m_needleDropShadow(needleDropShadow),
  107. m_dropDistance(dropDistance)
  108. {
  109. m_background = background;
  110. m_overlay = overlay;
  111. m_needleCenter.setXY(needleCenter.getX(), needleCenter.getY());
  112. m_needleColour = needleColour;
  113. startTimer(m_decayTime);
  114. }
  115. //==============================================================================
  116. MeterComponent::~MeterComponent()
  117. {
  118. stopTimer();
  119. }
  120. //==============================================================================
  121. void MeterComponent::buildImage(void)
  122. {
  123. // Build our image in memory
  124. int w = getWidth() - m_inset*2;
  125. int h = getHeight() - m_inset*2;
  126. if(m_meterType == MeterHorizontal)
  127. {
  128. m_img = Image(Image(Image::RGB, w, h, false));
  129. Graphics g(m_img);
  130. g.setGradientFill (ColourGradient(m_minColour, 0, 0, m_thresholdColour, w * m_threshold, 0, false));
  131. g.fillRect(0, 0, int(w * m_threshold), h);
  132. g.setGradientFill (ColourGradient(m_thresholdColour, int(w * m_threshold), 0, m_maxColour, w, 0, false));
  133. g.fillRect(int(w * m_threshold), 0, w, h);
  134. if(m_segments)
  135. {
  136. // Break up our image into segments
  137. g.setColour (m_backgroundColour);
  138. g.fillRect (0, 0, w, m_markerWidth);
  139. g.fillRect (0, h-m_markerWidth, w, m_markerWidth);
  140. for(int i = 0; i <= m_segments; i++)
  141. g.fillRect(i * (w/m_segments), 0, m_markerWidth, h);
  142. }
  143. }
  144. else if(m_meterType == MeterVertical)
  145. {
  146. m_img = Image(Image::RGB, w, h, false);
  147. Graphics g(m_img);
  148. int hSize = (int)(h * m_threshold);
  149. g.setGradientFill (ColourGradient(m_minColour, 0, h, m_thresholdColour, 0, h - hSize, false));
  150. g.fillRect(0, h - hSize, w, hSize);
  151. g.setGradientFill (ColourGradient(m_thresholdColour, 0, h - hSize, m_maxColour, 0, 0, false));
  152. g.fillRect(0, 0, w, h - hSize);
  153. if(m_segments)
  154. {
  155. // Break up our image into segments
  156. g.setColour (m_backgroundColour);
  157. g.fillRect (0, 0, m_markerWidth, h);
  158. g.fillRect (w-m_markerWidth, 0, m_markerWidth, h);
  159. for(int i = 0; i <= m_segments; i++)
  160. g.fillRect(0, i * (h/m_segments), w, m_markerWidth);
  161. }
  162. }
  163. // Only build if no other analog images exist
  164. else if(m_meterType == MeterAnalog && !(m_background.isValid() || m_overlay.isValid() || m_needleLength))
  165. {
  166. m_img = Image(Image::RGB, w, h, false);
  167. Graphics g(m_img);
  168. g.setColour(m_backgroundColour);
  169. g.fillRect(0, 0, w, h);
  170. const double left = 4.71238898; // 270 degrees in radians
  171. const double right = 1.57079633; // 90 degrees in radians
  172. double startPos = m_minPosition; // Start at 10%
  173. double endPos = m_maxPosition; // End at 90%
  174. float strokeWidth = m_segments; // We get our wiper width from the segments amount
  175. double pos;
  176. float radius = jmax (w/2, h/2) - strokeWidth/2;
  177. float angle;
  178. float x;
  179. float y;
  180. // Create an arc with lineTo's (works better than addArc)
  181. Path p;
  182. for(pos = startPos; pos < endPos; pos += .02)
  183. {
  184. angle = left + pos * (right - left);
  185. x = sin(angle)*radius + w/2;
  186. y = cos(angle)*radius + h - m_segments;
  187. if(pos == startPos)
  188. p.startNewSubPath(x, y);
  189. else
  190. p.lineTo(x, y);
  191. }
  192. angle = left + pos * (right - left);
  193. p.lineTo(sin(angle)*radius + w/2, cos(angle)*radius + h - m_segments);
  194. // Create an image brush of our gradient
  195. Image img(Image::RGB, w, h, false);
  196. Graphics g2(img);
  197. g2.setGradientFill (ColourGradient(m_minColour, 0, 0, m_thresholdColour, w * m_threshold, 0, false));
  198. g2.fillRect(0, 0, int(w * m_threshold), h);
  199. g2.setGradientFill (ColourGradient(m_thresholdColour, int(w * m_threshold), 0, m_maxColour, w, 0, false));
  200. g2.fillRect(int(w * m_threshold), 0, w, h);
  201. // Stroke the arc with the gradient
  202. g.setTiledImageFill (img, 0, 0, 1.0);
  203. g.strokePath(p, PathStrokeType(strokeWidth));
  204. }
  205. }
  206. //==============================================================================
  207. void MeterComponent::setBounds (int x, int y, int width, int height)
  208. {
  209. Component::setBounds(x, y, width, height);
  210. buildImage();
  211. }
  212. //==============================================================================
  213. void MeterComponent::setFrame(int inset, bool raised)
  214. {
  215. m_inset=inset;
  216. m_raised=raised;
  217. buildImage();
  218. }
  219. //==============================================================================
  220. void MeterComponent::setColours(Colour& min, Colour& threshold, Colour& max, Colour& back)
  221. {
  222. m_minColour = min;
  223. m_thresholdColour = threshold;
  224. m_maxColour = max;
  225. m_backgroundColour = back;
  226. buildImage();
  227. }
  228. //==============================================================================
  229. void MeterComponent::setValue(float v)
  230. {
  231. float val = jmin(jmax(v, 0.0f), 1.0f);
  232. if (m_skew != 1.0 && val > 0.0)
  233. val = exp (log (val) / m_skew);
  234. if(m_decayTime)
  235. {
  236. if(m_value < val)
  237. {
  238. // Sample and hold
  239. m_monostable=m_hold;
  240. m_value = val;
  241. repaint();
  242. }
  243. m_decayToValue = val;
  244. }
  245. else
  246. {
  247. if(m_value != val)
  248. {
  249. m_value = val;
  250. repaint();
  251. }
  252. }
  253. }
  254. //==============================================================================
  255. void MeterComponent::setDecay(int decay, int hold, float percent)
  256. {
  257. m_decayPercent = percent;
  258. m_decayTime = decay;
  259. m_hold = hold;
  260. if(m_decayTime)
  261. // Start our decay
  262. startTimer(m_decayTime);
  263. else
  264. stopTimer();
  265. }
  266. //==============================================================================
  267. void MeterComponent::timerCallback()
  268. {
  269. if(m_monostable)
  270. // Wait for our hold period
  271. m_monostable--;
  272. else
  273. {
  274. m_value = m_decayToValue + (m_decayPercent * (m_value - m_decayToValue));
  275. if(m_value - m_decayToValue > 0.01f)
  276. // Only repaint if there's enough changes
  277. repaint();
  278. else if(m_value != m_decayToValue)
  279. {
  280. // Zero out
  281. m_value = m_decayToValue;
  282. repaint();
  283. }
  284. }
  285. }
  286. //==============================================================================
  287. //
  288. // This needs to be made more efficient by only repainting when things actually
  289. // change, and only painting the area that has changed. Right now, this paints
  290. // everything on every repaint request.
  291. //
  292. void MeterComponent::paint (Graphics& g)
  293. {
  294. int h = getHeight() - m_inset*2;
  295. int w = getWidth() - m_inset*2;
  296. // Background
  297. if(!m_background.isValid() && !m_needleLength)
  298. {
  299. g.setColour(m_backgroundColour);
  300. g.fillRect(m_inset, m_inset, w, h);
  301. }
  302. // Bevel outline for the entire draw area
  303. if(m_inset)
  304. LookAndFeel_V2::drawBevel(
  305. g,
  306. 0,
  307. 0,
  308. getWidth(),
  309. getHeight(),
  310. m_inset,
  311. m_raised?Colours::white.withAlpha(0.4f):Colours::black.withAlpha(0.4f),
  312. m_raised?Colours::black.withAlpha(0.4f):Colours::white.withAlpha(0.4f));
  313. // Blit our prebuilt image
  314. g.setOpacity(1.0f);
  315. if(m_meterType == MeterHorizontal)
  316. {
  317. if(m_segments)
  318. {
  319. float val = float(int(m_value * m_segments)) / m_segments;
  320. g.drawImage(m_img, m_inset, m_inset, (int) (w * val), h, 0, 0, (int) (w * val), h);
  321. }
  322. else
  323. g.drawImage(m_img, m_inset, m_inset, (int) (w * m_value), h, 0, 0, (int) (w * m_value), h);
  324. }
  325. else if(m_meterType == MeterVertical)
  326. {
  327. int hSize;
  328. if(m_segments)
  329. hSize = (int) (h * float(int(m_value * m_segments)) / m_segments);
  330. else
  331. hSize = (int) (h * m_value);
  332. g.drawImage(m_img, m_inset, m_inset + h - hSize, w, hSize, 0, h - hSize, m_img.getWidth(), hSize);
  333. }
  334. else if(m_meterType == MeterAnalog)
  335. {
  336. if(m_background.isValid())
  337. g.drawImage(m_background, m_inset, m_inset, w, h, 0, 0, m_background.getWidth(), m_background.getHeight());
  338. else if(m_img.isValid())
  339. g.drawImage(m_img, m_inset, m_inset, w, h, 0, 0, m_img.getWidth(), m_img.getHeight());
  340. float angle = 4.71238898 + (m_value * (m_maxPosition - m_minPosition) + m_minPosition) * -3.14159265;
  341. if(m_needleLength)
  342. {
  343. g.setColour(m_needleColour);
  344. Line<float> l (m_needleCenter.getX() + m_inset,
  345. m_needleCenter.getY() + m_inset,
  346. sin(angle)*m_needleLength + m_needleCenter.getX() + m_inset,
  347. cos(angle)*m_needleLength + m_needleCenter.getY() + m_inset);
  348. g.drawArrow (l, m_needleWidth, m_arrowLength, m_arrowWidth);
  349. if(m_needleDropShadow)
  350. {
  351. int dropX = 0, dropY = 0, dropPointX = 0, dropPointY = 0;
  352. if(m_needleDropShadow == NeedleLeft)
  353. {
  354. dropX = -m_dropDistance;
  355. dropY = 0;
  356. dropPointX = -m_dropDistance;
  357. dropPointY = 0;
  358. }
  359. else if(m_needleDropShadow == NeedleRight)
  360. {
  361. dropX = m_dropDistance;
  362. dropY = 0;
  363. dropPointX = m_dropDistance;
  364. dropPointY = 0;
  365. }
  366. else if(m_needleDropShadow == NeedleAbove)
  367. {
  368. dropX = 0;
  369. dropY = 0;
  370. dropPointX = 0;
  371. dropPointY = -m_dropDistance;
  372. }
  373. else if(m_needleDropShadow == NeedleBelow)
  374. {
  375. dropX = 0;
  376. dropY = m_dropDistance;
  377. dropPointX = 0;
  378. dropPointY = m_dropDistance;
  379. }
  380. g.setColour(Colours::black.withAlpha(0.2f));
  381. Line<float> l (m_needleCenter.getX() + m_inset + dropX,
  382. m_needleCenter.getY() + m_inset + dropY,
  383. sin(angle)*m_needleLength + m_needleCenter.getX() + m_inset + dropPointX,
  384. cos(angle)*m_needleLength + m_needleCenter.getY() + m_inset + dropPointY);
  385. g.drawArrow(l, m_needleWidth, m_arrowLength, m_arrowWidth);
  386. }
  387. }
  388. else
  389. {
  390. // Contrasting arrow for built-in analog meter
  391. g.setColour(m_backgroundColour.contrasting(1.0f).withAlpha(0.9f));
  392. Line<float> l (w/2 + m_inset,
  393. h - m_segments + m_inset,
  394. sin(angle)*jmax (w/2, h/2) + w/2 + m_inset,
  395. cos(angle)*jmax (w/2, h/2) + h - m_segments + m_inset);
  396. g.drawArrow(l, m_needleWidth, m_needleWidth*2, m_needleWidth*2);
  397. }
  398. if(m_overlay.isValid())
  399. {
  400. g.setOpacity(1.0f);
  401. g.drawImage(m_overlay, m_inset, m_inset, w, h, 0, 0, m_overlay.getWidth(), m_overlay.getHeight());
  402. }
  403. }
  404. }
  405. END_JUCE_NAMESPACE