Graphics Functions
-
Just to show you what I'm up to, this is my GUI now and everything, with the exception of the knobs, has been created using panels and paint routines.
-
Alright, there you go:
https://github.com/christophhart/HISE/commit/b7d6f80e3f57eeb1fd848502e0957d5299305469
This is the example code.
Content.makeFrontInterface(600, 500); // Just so that you can see the drop shadow :) const var bg = Content.addPanel("bg", 3, 4); // [JSON bg] Content.setPropertiesFromJSON("bg", { "width": 100, "height": 100, "itemColour": 4285415745, "itemColour2": 4285547331 }); // [/JSON bg] const var Panel = Content.addPanel("Panel", 20, 20); // [JSON Panel] Content.setPropertiesFromJSON("Panel", { "width": 50, "allowCallbacks": "Clicks, Hover & Dragging" }); // [/JSON Panel] Panel.setPaintRoutine(function(g) { // 1. Draw the drop shadow g.setColour(0x88000000); g.fillEllipse([6, 2, this.getWidth()-10, this.getHeight()-10]); g.addDropShadowFromAlpha(0x88000000, 8); // 2. Draw the knob g.setColour(Colours.grey); g.setGradientFill([0xFF666666, 0.0, 0.0, 0xFF444444, 0.0, 50.0]); g.fillEllipse([5, 0, this.getWidth()-10, this.getHeight()-10]); // 3. Rotate the canvas. // Note that the pivot centre is not the mid point of the panel because the ellipse is smaller g.rotate(this.getValue() * 1.5 * Math.PI, [25, 20]);; // 4. Draw the dot g.setGradientFill([0xFF111111, 0.0, 0.0, 0xFF222222, 0.0, 50.0]); g.setColour(0xaa000000); g.drawEllipse([15, 25, 7, 7], 1.0);; g.fillEllipse([15, 25, 7, 7]); }); Panel.setMouseCallback(function(event) { if(event.clicked) { this.data.mouseDownValue = this.getValue(); } else if (event.drag) { var distance = event.dragX - event.dragY; // change this for sensitivity var normalisedDistance = distance / 200.0; var newValue = Math.range(this.data.mouseDownValue + normalisedDistance, 0.0, 1.0); this.setValue(newValue); this.changed(); this.repaintImmediately(); } });
-
Yippie!!!!
-
Nice one.
-
And I added
g.drawAlignedText()
:Content.makeFrontInterface(600, 500); const var Panel = Content.addPanel("Panel", 24, 30); // [JSON Panel] Content.setPropertiesFromJSON("Panel", { "width": 214, "height": 136 }); // [/JSON Panel] Panel.setPaintRoutine(function(g) { // I can't help myself... g.setFont("Comic Sans MS", 24.0); g.setColour(Colours.white); g.drawAlignedText("Funky Alignment", [0, 0, this.getWidth(), this.getHeight()], "centredTop"); });
-
I'm starting to love this framework more and more, these vector api calls remind me of scripting CSS3 with javascript :) . Awesome. David are those black menus HISE native or are those images???
-
The black dropdown is HISE native.
-
Ok thanks.
-
If I want the knob panel range to be -100 to 100 (or anything other than 0.0-1.0), is the best approach to leave it as 0-1 and use a linear interpolation function to convert the normalized value to the desired range? Or can I just set the knob/panel's min/max to the desired ranged and make some slight adjustment to the algorithm?
-
Just scale it using basic math:
var normalizedValue = Math.range(this.data.mouseDownValue + normalisedDistance, 0.0, 1.0); var value = this.data.min + normalizedValue * (this.data.max - this.data.min); this.setValue(value);
-
Excellent, thanks.
I did it a little differently to how you suggested so I'll paste my altered version here. I'm storing the normalized value as well as the actual value so that I can use one for the paint routine rotation and the other for setting parameters. I also added support for double click to set the knob to 0.
HiseSnippet 1494.3ocsW8+aZbCE+HMHMXCo0o8GfE+vzwFkdPfjrTUslPBsocIEU5R6TTTm4NCmUty9jOCDZU9ed+Gr8r8cbGTRVYZyJPvuu32m2yum8y8EbWRbLWXU3qe67HhUguo3f4LoeWeLkYc5wVEpT7UL9vStAGFEPrNZdDNNl3YUnvCdtRjBk11RO9ye4Hb.l4RxHYYcAm5R9UZHUlQ8Od1qnAA8vdj2RCyIc6mcpKm0kGvm.v4AEcrhvtWiGSNGqDaqhVSojYwVEbJtSqFhqaN7iG1Habjyzme36a68yC7Uj6pn0WKfk6DgfvjW.paUnXg+BFEJdhGUxECjXIAVysOh6MefOeFyX5Knwzgf+BSZZM.vjg7oLIQLB6RxIpUWeZfW+zHYrErZ8yhqOvDW+9hmQ8nKnmEe+VMCTlF4CwE159fYy6Bl83AdpE3NfWgbvaaC7dXwAtBZjLiiBaeWB06.cFlVacXohc4PjgIaDhulzS.SVDor20woNpiiSsmTobkxO9wnWNIVhh4HoOVhlymfbwLTLg.DHHOAOBE6i83yPGTqRYHk.jdJVfFNF8TTpcvdd8wLRfc0giqVGsScTSm1JK.q+kubvqOGD+pJkSEOln7rHhPRIw.9BUhjn6mpTFgpNi5I8qd.rLN00D7Izw9x7TnRRnI8Dn1t09cZ2ryds6rJyVIb6zduc1oYkx2lBqGmgq79k1OVqqo+GfvVP.rYKmkbOMu+YOLcIV1I6j3Q3f.9rtv2CgJsXfQ0tAT3W0QufOkHP+.5XAd7XJChSZEBorpG7nEQjP7MUO.l8YNYJ5pTV+KM5fDd4a3SjTFwdzDlqjxY1igsX.ak.Ua1PYsYqlE.LGqz2DbsctY+8czCkEAVifCSNIHfFESrubWHXUGVAZbiwD46TtqcM.uYzdgdaUS7pjU.B3GC1af1bpP2gAQ93bVpNZesnFb1JGNuFNZbE.Z9GXKAYdhA.lOWf8nv9TO.s1W5bSud6pG0QNMbR+Bn1VORHzA99p04lc.A1P2zf8cZfdCWcVhF8Pc2TbbCjZuzL.QNmq4BklJQhnS4RjKfbAAQiQLtgdH0CEwgcTDejQPcV7PhKdRrY0IFzpzJNDxwHBsiHz12VCTOrD2fwE.a5GIdWfClPP+HjIrK78YXoei9mVGcYqNpZ.vOxbj14SV3x6NL2TOVML2ROVSXNehFFuThlGXuE6.MUPB9rG72UP0YCGC5VYiZIwTqyslSAyUWbFGBXo0fYEFjofajTbPGYl1vUUcR7.xkTzKkEDCUqxwv8BlX3SQ2Y7UgxagOj.0dyHTxR6wm.WjnK+Wyxu5dzSgHWmmrPFvMzzsMwJCQWeLaLwyNGIAIRcJvoggDOJjEDLG3td3.m6r.GpSI8nwRU2Efoyj38nGka1uqMj5CjfXrtNLfFwEv8KrXpjNkJmmtjINEbY1wYK9B6.E5NPhwSPoKZNUxEGzYoBksruycieZMlJI2Sm3nxGt+n8+EVAJt+s3jHxTsFTFRuefDlik0AJc8IjAtjaSlsTdEb.is9tfZYVMW8qcNgfaHpA6RqnVsEaUv8Ww7.RiHA.D6TCkwe47qk4uQoYlzdn9KsDCwYpy5dMytV4OUtT4aKuJmQiVGK0MtBt57r0wU0Sq3dTylMIbHQT2rEjJGzp1xM5U9KqQOWys+4DjyNkQkuNhrXtwISo3Xsv4xSJyoxSU6LqQrLRed2kVIcj.+5yayDZ1k6MI.KWtCX0KIRX.Aqk5wT0Gopxcd9WZrAsE6r11hatNf+EB2GVrOU55ud7t0ZvKzw8+23M4QFUJdxnQDWYFX2tXu2+u8EEa.TL80MFNlRPuAdi04SBG.Wh5R.jvfK5hUu6ZKUxgYtiZtJxLfv7bReTVBylp4ERX1LkoUH1Uv+fqICT8LluRSAvDS+.wREOSMG0zRWZYvMzmB8Cttp.wi.judMZswZryFqQ6MViNarF6twZr2Fqw92iFpGxd3DIOzTVXY82vSTZpf
-
Christoph, is there way to prevent the panel knobs and sliders from resetting to zero every time the script is compiled or the preset is loaded? I've tried with the knob example you posted.
-
Panel.set("saveInPreset", true);
If you set this property, it will save the value before recompiling and executes the control callback after compilation with the stored value (after setting the value internally). By default it's disabled for ScriptPanels, so you need to enable this specifically.
The general rule of thumb is: Enable this property for everything that controls sound parameters so you want this to be restored when you load user presets), but disable this for GUI controls (like buttons that toggle the visibility of UI pages, etc.). And definitely disable those for buttons that load user presets or you get an infinite loop...
-
I just realised that it wasn't set for your example, but for I already have set it in my own project. I shall investigate further.
-
I think it's something in my factory function but I haven't worked out what yet...
HiseSnippet 1643.3ocsX8uaaaCDVNMBXxadXcXO.b4OFr2bjkiS5ZSPPahSbmwVZMp6BZQwPGsDsMWjHMnnbhaPdI1S5dC1NRJYImXGDWfI3XKd2Qd28ce7Womf6Shi4BqRe4amMgXU5qr6OiIG2dLlxr5dhUou19LbrjHPFQGOaBNNlDXUpzidoRPImMszO+yyOFGhY9jbQVVmyo9jeiFQk4R+yW7qzvvN3.xaoQErd2Wz0myZyC4IP77HaOqIX+KviHuBqLaCaqoTxkwVk7rasiq3hlC9zQtpmiUe4416L2STuM72cGbpVg6a6fmcjqkehPPXxygtaUxtz+BOkrOMfJ4h9Rrj.i4lGyCl0eL+Rlw0mSioCBIpFMs5CwjQbWF.ECw9jBlZ0dLMLnWFTFaAiVubf8QFf86rOiFPmKOGf+FsBTdOJBwk139BylqJL6vCCTCvJBuREBuMMg2is66KnSj4ZTw12lJcEQmQo0F+cY61b.YXR2H7EjNBnwbjp5S77pi1wyq1AUJWobiF8fLWhD7DIkQpTdTHe.NDcAiOvn4PzvDlujxYUGUC0nwKMFLoX2PLBIHFI4nQbz.xPtfnGAjuffUcsR4qqT1QNlF6NhHqtEggAbJXqZnCODIEIDzyQibiIRCeqp2Uc5bzQGs2yZVCs+szbjWpFHAbF4NDXumFFRmDSp9gl0Qvmb+bIMPNdqZa2pnvwD5nwRkz+HcLVzwszO0N.AY6IXwEnWJHyz1EHvW9Y6q5nVFLGFHAWwfppsK.KwtLtHBGR+DI3bbH.G+Hpo6tsfeNCKG61qaczGtimZryx7TicLYkSkxHjNE3x0F6e1y1s8tOYYXepFM37FRvRJA.8pE72SgO5H4FcRCqkDKQSwBfXL.HUFBFiDVErE9bsNo1eO3USlneUyxdigjs+bNYcTDks+1MUL4H7U6qeIfLDmDJ0v29d2.QXiFsUzOCUzP16f8gouylSoqTlxBUD3LAEhqqpilUGwG7W0LrWUrCYgTvCg3OaFFNHvX9VPdsUcjpWok4TiU.XVMSOdt520EoELIsBZrwz3tFgCC4W1F9d.rZbLX7VsCova0Q+BeJruvOfNQfGMhxFs0c6bLdJoKqmf.sftpJ920H.aSiA3sknFeUlZ7UKnVSjKVF.XRYWQQ2Z35Un9VUYawBdFM1oQCzIjgpxDF4ml5vxLiwSo.sDc4XBCMimfhIgDeIXzD9jjInHBKA10YQOdFOIljgeUmuzFYJTLgBsipR6PGhLRb8UfKIPownREL8AXDl4Q.5rJKGJ3Q5lQpwFo6h1z7o2ZMm.6SjgKqbluNmuQ8EILlj617PJfm.ye0UccX4bsi9GsI4i6BEhuGlpOkSM4Q1PVL.u85OGhptpwZ6BK5n3J0fcERc7nLBRskXUsCz9zonugBhdPWguLLfT7H8aErfJfF.aOEFL1B0Gn75mDpl4qpJwDVLURmRkyRKXCvvNlHXxddMTfYiHlAPMOOfFKUGdBvgb27NHoxa89CLKvl833rPq4Ai+X0PqQCDrwXwvI2eovODVmj644AQCzddtdZr3ttn.WUJBUbL0tzcGhTMa3GEftfLCANeLIL.E.5S4.nzBwCw+65kE.5xvRxz6iJo2CSivUW4TheZIw.rwfKrxdSWO8p4+dbJLZJYT1sNDhBc0arpWXOinoSOxkKLwKmUB9891D9APqOX0vwb1cl+S4yFhrS9ZDBhNS5FEQBn.uMbV0TS0pMTnfTYpdey70FEDYhfkswjZ614akwYuhKIulUsV4qK6T9lx2VyvgKSUayPERDKSq5RBh6oaUYIQCHh5lhTlcvYeW7jykeXmb12rKaAC4rtLp70SHyaaRxLIdVySthhxSphR0IyRLKWzcOttU5N+pCtmZNXpTer8JYGaWch.KJbpbaa3XAVZrH+pUu+EV28H+vEO3ApUsV71Hpa0kp.v4ENuu5L8p0RlU7VeqwUT7V5UTZtrb9AFtO1tGU5Od4w6FKIdAP7+63M8BeUrOc3P3zA4A6l1cd2m6s6ViPwbXlQvZfB5U.g3UIQ8gSs3SfHgAzjX0cf2PwqLs8TsUHSeBKvK6BxoJapZWJUYyLkVQXeA+ioqAntR4Wnk.wDSeYcG6yTsQMyXhp3NBtm6G88U.w1Pju7dryZ2iVqcO1cs6wdqcOdxZ2ieds6wSumdn9mJbThjGYlVXY8eTFlgnE
-
The problem is that you're using the normalised value in the paint callback, so while the actual value is restored correctly, you're simply not painting it - it will not restore the complete object, but just the value passed in
setValue
. The other data should be either constant or derived from this value.There are a few other issues with the example:
- use
local control = ...
instead ofvar
- Why did you use a global variable for the paint callback? Just handle it like the event callback. In general I advice to use
global
variables only when necessary.
- use
-
Aha I see, so I'll have to convert the value. What's the advantage of using local, is it just for encapsulation?
I'm using global because in my project that the function is taken from I'm calling the function from within other namespaces and it wouldn't work unless I set it to global, but actually I had to move the position of the functions to before the other namespaces anyway so I think I could set them to const var now instead of global and it should work.
-
Seems to all be working now, thanks for your help. Here's the latest version for anyone interested.
HiseSnippet 1599.3ocsW01aaaCDVNMZXxadXcXec.r9CCxaN9k3zr1DDzl5T2YrkV24tfNTLzQKQayEIRCJJ63Fj+y6ev1QRIK4WRPcAlfih38B4cO2w6H6I3djnHtvpvW7l4SHVE9R69yYxwsGioLqtmYU3qrOGGIIBjgzylOAGEQ7sJT3duPQnfytV5m+4IOCGfYdjLRVVWvodjekFRkYT+qm9KzffNXexang4j9fm10iyZyC3wf8bO6FVSvdWhGQdIVI1N1VSojYQVEZX2Z+ZhKaN3CmVS8Ls1qwQu9rZ21ikWrPPXxK.0sJXW3egmB1O2mJ4h9Rrj.y4tOi6Ou+X9LlYoufFQGDPTCZZ0GrIC4tL.JFh8H4D0p8XZfeuTnLxBlsdY.68L.62ZeN0mtfdF.+0ZFnLMxCwE14tLyl2lY1gG3qlfaw7Jjy710Xd22tumfNQlwQYaeSB0aw5LLs146JZ2lCHCSVKDeIoi.Fr.obOrQipn8aznxwkJVpX858.OWhD7XIkQJUDh4QRzTr.cIiOvv7DzvXlmjxYtipfpW+EA7A3.zj7ZhXDheDRxQi3nAjgbAQOCHOAAqTsTwqKUzQNlFUaDQ5Vlvv.T4WtB5jSPRQLA8DznZQDoIkyswUc5b5om9vG2rB5nU3bZiDNfO3Lp1PHA94AAzIQD220rJB9ksNyn9xwkqrWq7DGSniFKUT+yj4X4Etk9oxwHvaOCKtD8BAYtVNeAd1m7ZUE0x.6NJ.lwEg3.5GH9WfC.++DjapVZBtUP6kadBorxU.3OSJfF9pxaRJkOoMWAWkp5t5R8Cnl0NnE7uywxw050sJ5cq4E02eSdQ88MHFL8HjFd3xsNt93GeP6CNbSw0DNZf+2H9aH7BYusf+dD7SaI2nAz7osC.jzj7xHAtfrvuq0N0QOD9z3I5O0Yv+lIA9nE46UQ.HdzdMUaT..9H8G9jg33.Sf4nF2.VX85sUo1lzbydoNXOn5v7EaWJUjxBTaNRIjyttpJZdUDeveWwryHf6A6o.+PJ3AfGjtEF66aTnL3YkqhT5kjDkHrBBSiZ5Yrl9acXZIQRhgFYLCVWHbP.eVa38.nbeDHb41AT3qpnelOEZ778nyD3QinrQkWW4H7TRWVOAAFAppB+qKjJE0XCvWafMjSmvFe0Rr8wRbs7AB.lTxkmzJSWubQXWkr4C4oIxN0qiNiLTEnvHuDWGJhMFOkBIlnYiILzbdLJhDP7jfPS3ShmfBIrXng1xq3473HRJ94tnvIYJDLgPsiJV6PGhLTp4o.WhuhigkxX5CvHr2i.IzJubnfGpGFplajVEsn5ccZTQy4LnQTJtrRkDsmdi5EIHhjsXYFhOOF12pi0Ziw4ZG8+zhjsPKA+O.1hOkSMVe5TlXVQoq8l00XQoBKH53R2vPhOE1SEL2cYA7FiYiH9Knpckj2JOBkyQfzyDOvHK.nP7vKNPsYUAiQDVDURmRkySP3AXnGJB1elA5B0BZl.UYEeZjTcbJ.ZyVl2BkdyF8GGapIl933bqixAsIoARQfJ7oZu1cHRMrtWnO5RxbDMBpYE3i7A9I.MJAaxYUK9rN5vZMxfIz5Kqt4CYVZphtGf1ccu0Dpebw7Wc0lMKQP0ORWa72iHZ5InIksxwEfCHfzMmTUEWxBQ2wPEfsvxevZY42YdXpdUN14SL0y.oNYodpW2rnJhfHiErzR3pVSKJ6yYujKIuh4Vo30EcJdSwU4Lb3lX01LUADwl3pNut3NTykEGNfHpZBAoxAGCc4CwV7i6Prdl9Q4Djy5xnxWMgrXrwISozvZgykmTlSkmp1Y1fXYjV+jyVI8HUmgNQbPTo9DzkROAsp2oEENfrsMz.0RiEvsb9rz6DYs9oug6.v8UkKV9hApKXkv.v4kN5s530pZJyyeArs31BM13sEZtIe9izbuucOpza7ls2c1f8Bf3+21axcuJY+7gCg9nYF6t1cd6m5Es1BSwz1eDTuSPuBRHdYbXen+tGArDFjlDotN5Np7Jy3FpwJjoOg42H8tpILapFWHgYyTlVgXOA+8I0.T2t6y0T.ahou2rCbAdXLpYZlnxtCgqb9dOOEPrGX4aVi82ZMZs0ZbvVqwC2ZMNbq03m1ZMdzcng598mFK4glsEVV+WhWE+5
-
I've took a look at the code and made a few adjustments according to a few best practices (this is basically how I write all my custom panels):
- No need to define the paint function outside the factory method.
- Add the name as parameter (this is important as soon as you want to duplicate the control).
- change the name to
createXXX
. Then you need this parameter order:(name, x, y, ...)
. This trick allows the interface designer to recognise it as factory function and allow moving around the control - click and drag it in edit mode and note how the x and y position get updated. If you duplicate the control, it will just copy the line (and even copy your object argument). This makes building interfaces extremely convenient. - Some minor coding style suggestions which save you typing and makes things a bit more clear (I left the old code in there so you can see the difference)
.
This is the updated code for the knob:
/** Creates a vectorized knob. */ inline function createVectorKnob(name, x, y, obj) { local control = Content.addPanel(name, x, y); control.set("width", obj.width); control.set("height", obj.height); control.set("allowCallbacks", "Clicks, Hover & Dragging"); control.set("saveInPreset", true); control.set("min", obj.min); control.set("max", obj.max); control.data.defaultValue = obj.defaultValue; control.setPaintRoutine(function(g) { // this.get("enabled") == true ? g.setColour(0xFFAAA591) : g.setColour(0xA0AAA591); // Better: g.setColour(this.get("enabled") ? 0xFFAAA591 : 0xA0AAA59); // g.fillEllipse([1, 1, this.get("width")-3, this.get("height")-3]); // Better: g.fillEllipse([1, 1, this.getWidth()-3, this.getHeight()-3]); g.setColour(0xFF333333); //Dark Grey g.drawEllipse([1, 1, this.get("width")-3, this.get("height")-3], 3); var normalizedValue = (this.getValue() - this.get("min")) / (this.get("max") - this.get("min")); g.rotate(normalizedValue * 1.43 * Math.PI, [this.get("width")/2, this.get("height")/2]); //Dot this.get("enabled") == true ? g.setColour(0xFF994C46) : g.setColour(0xA0994C46); //Red g.fillEllipse([10, 30, 8, 8]); }); // Define a callback behaviour when you select a popup menu... control.setMouseCallback(function(event) { if (event.clicked) { // Save the value from the mouse click this.data.mouseDownValue = this.getValue(); } else { if (event.doubleClick) { if (this.data.defaultValue !== void) { this.setValue(this.data.defaultValue); this.repaintImmediately(); this.changed(); } } else if (event.drag) { // Calculate the sensitivity value based on the value range var distance = event.dragX - event.dragY; if (event.ctrlDown) //If ctrl/cmd key is held down { distance = distance / 6.0; } var newValue = Math.range(this.data.mouseDownValue + distance, this.get("min"), this.get("max")); //Use this value in paint routine for rotation if (newValue != this.getValue()) { this.setValue(newValue); this.repaintImmediately(); this.changed(); } } } }); return control; } const var knb = createVectorKnob("knb", 0, 0, {width:50, height:50, min:-100, max:100, defaultValue:0});
But the idea with the object argument is really nice - I'll think I adapt this from now on :)
-
Thanks Christoph. I was passing the paint routine as a separate function so that the knob creation could be generic and could be used for many different knob designs depending on the paint routine that is passed.
Yeah I like passing an object into inline functions to get around the 5 parameter limit :)