GradientEditor.hx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  1. package h2d.comp;
  2. import h2d.css.Defs;
  3. import h2d.css.Fill;
  4. private typedef Key = { x:Float, value:Int };
  5. private enum KCursor {
  6. KAlpha;
  7. KColor;
  8. }
  9. private enum CompStyle {
  10. CLabel;
  11. CInput;
  12. CInputSmall;
  13. }
  14. private class Style {
  15. public function new () {
  16. }
  17. public static function get(kind:CompStyle) {
  18. var style = new h2d.css.Style();
  19. switch(kind) {
  20. case CLabel: style.fontSize = 12;
  21. case CInputSmall: style.width = 24;
  22. style.height = 10;
  23. style.fontSize = 11;
  24. case CInput: style.width = 50;
  25. style.height = 10;
  26. style.fontSize = 11;
  27. }
  28. return style;
  29. }
  30. }
  31. private class CFlag extends h2d.css.Fill {
  32. public function new (parent, x:Float, y:Float, ang = 0., color = 0xff000000, border = 0xFFaaaaaa) {
  33. super(parent);
  34. var fill = this;
  35. fill.rotation = -ang;
  36. fill.x = x;
  37. fill.y = y;
  38. //bg
  39. // bgarrow
  40. fill.addPoint(-5, -4, border);
  41. fill.addPoint(5, -4, border);
  42. fill.addPoint(0, 0, border);
  43. fill.addPoint(0, 0, border);
  44. // bgsquare
  45. var dy = -5;
  46. fill.addPoint(-5, -9 + dy, border);
  47. fill.addPoint(5, -9 + dy, border);
  48. fill.addPoint(-5, 1 + dy, border);
  49. fill.addPoint(5, 1 + dy, border);
  50. // arrow
  51. fill.addPoint(-4, -5, color);
  52. fill.addPoint(4, -5, color);
  53. fill.addPoint(0, 0, color);
  54. fill.addPoint(0, 0, color);
  55. // square
  56. fill.addPoint(-4, -8+dy, color);
  57. fill.addPoint(4, -8+dy, color);
  58. fill.addPoint(-4, 0+dy, color);
  59. fill.addPoint(4, 0+dy, color);
  60. }
  61. }
  62. private class Cursor extends h2d.Sprite {
  63. var gradient : GradientEditor;
  64. public var value(default, set):Int;
  65. public var coeff(get, null):Float;
  66. public var color:Int = 0xFFFFFFFF;
  67. public var bgcolor:Int = 0xFFFF00FF;
  68. public var cursor:h2d.Sprite;
  69. public var kind:KCursor;
  70. var ang:Float;
  71. var interact:h2d.Interactive;
  72. var flag:CFlag;
  73. public function new(gradient,ix, iy, kind, value, ang, parent) {
  74. super(parent);
  75. this.gradient = gradient;
  76. x = ix;
  77. y = iy;
  78. this.value = value;
  79. this.ang = ang;
  80. this.kind = kind;
  81. init();
  82. }
  83. function set_value(v) {
  84. value = v;
  85. if(flag != null) {
  86. switch(kind) {
  87. case KColor: color = value;
  88. case KAlpha: color = (255 << 24) | (value << 16) | (value << 8) | value;
  89. }
  90. flag.remove();
  91. flag = new CFlag(cursor, 0, 0, ang, color, bgcolor);
  92. }
  93. return value;
  94. }
  95. function get_coeff() {
  96. return x / gradient.boxWidth;
  97. }
  98. function init() {
  99. cursor = new h2d.Sprite(this);
  100. switch(kind) {
  101. case KColor: color = value;
  102. case KAlpha: color = (255 << 24) | (value << 16) | (value << 8) | value;
  103. }
  104. flag = new CFlag(cursor, 0, 0, ang, color, bgcolor);
  105. interact = new h2d.Interactive(10, 14, this);
  106. interact.x = -5;
  107. if(kind.equals(KAlpha))
  108. interact.y = -14;
  109. interact.onPush = function(e) drag();
  110. interact.onRelease = function(e) stopDrag();
  111. }
  112. public function drag() {
  113. interact.startDrag(function(e) {
  114. if( e.kind == EMove ){
  115. setCursor(x + (e.relX-5));
  116. if(e.relY < - 6 || e.relY > 16 + 6)
  117. gradient.dragOut = true;
  118. else gradient.dragOut = false;
  119. }
  120. gradient.dragTarget = this ;
  121. });
  122. gradient.updateTarget = this;
  123. this.parent.addChild(this);
  124. }
  125. public function stopDrag() {
  126. interact.stopDrag();
  127. if( !visible ) remove();
  128. gradient.dragTarget = null ;
  129. gradient.dragOut = false;
  130. }
  131. public function select() {
  132. bgcolor = 0xFFFF00FF;
  133. flag.remove();
  134. flag = new CFlag(cursor, 0, 0, ang, color, bgcolor);
  135. if(gradient.colorPicker.visible)
  136. gradient.colorPicker.color = color;
  137. }
  138. public function unselect() {
  139. bgcolor = 0xFFAAAAAA;
  140. flag.remove();
  141. flag = new CFlag(cursor, 0, 0, ang, color, bgcolor);
  142. }
  143. public function setCursor(px:Float) {
  144. x = Math.max(0, Math.min(gradient.boxWidth, px));
  145. }
  146. }
  147. private class AlphaSelector extends h2d.Sprite {
  148. public var target(default, set):Cursor;
  149. var gradient : GradientEditor;
  150. var title:h2d.comp.Label;
  151. var slider:h2d.comp.Slider;
  152. var alphaInput:h2d.comp.Input;
  153. var locLabel:h2d.comp.Label;
  154. var locInput:h2d.comp.Input;
  155. public function new (gradient,ix, iy, parent) {
  156. super(parent);
  157. this.gradient = gradient;
  158. x = ix;
  159. y = iy;
  160. init();
  161. }
  162. function init() {
  163. title = new h2d.comp.Label("Alpha", this);
  164. title.addStyle(Style.get(CLabel));
  165. slider = new h2d.comp.Slider(this);
  166. slider.x = 55; slider.y = 4;
  167. slider.onChange = function(v) { updateSlider(v); };
  168. slider.value = 1;
  169. alphaInput = new h2d.comp.Input(this);
  170. alphaInput.addStyle(Style.get(CInputSmall));
  171. alphaInput.x = 170;
  172. alphaInput.value = "255";
  173. alphaInput.onChange = function(e) {
  174. var v = Std.parseFloat(alphaInput.value);
  175. if (!Math.isNaN(v)) {
  176. v = Math.min(255, v) / 255;
  177. updateSlider(v);
  178. slider.value = v;
  179. }
  180. };
  181. locLabel = new h2d.comp.Label("Location %", this);
  182. locLabel.setStyle(Style.get(CLabel));
  183. locLabel.x = 255;
  184. locInput = new h2d.comp.Input(this);
  185. locInput.setStyle(Style.get(CInput));
  186. locInput.x = 320;
  187. locInput.value = "100.00";
  188. locInput.onChange = function(e) {
  189. var v = Std.parseFloat(locInput.value);
  190. if (!Math.isNaN(v)) {
  191. v = Math.min(100, v);
  192. target.setCursor( v * gradient.boxWidth / 100 );
  193. }
  194. };
  195. }
  196. public function update() {
  197. if(target == null)
  198. return;
  199. locInput.value = Std.string(Math.floor(target.coeff * 100 * 100) / 100);
  200. }
  201. function set_target(cursor:Cursor) {
  202. target = cursor;
  203. var v = target.value / 255;
  204. slider.value = v;
  205. locInput.value = Std.string(Math.floor(target.coeff * 100 * 100) / 100);
  206. updateSlider(v);
  207. return target;
  208. }
  209. function updateSlider(v:Float) {
  210. var alpha = Math.round(255 * v);
  211. alphaInput.value = Std.string(alpha);
  212. if(target == null)
  213. return;
  214. target.value = alpha;
  215. }
  216. }
  217. private class ColorSelector extends h2d.Sprite {
  218. public var target(default, set):Cursor;
  219. var gradient : GradientEditor;
  220. var title:h2d.comp.Label;
  221. var locLabel:h2d.comp.Label;
  222. var locInput:h2d.comp.Input;
  223. var colorInput:h2d.comp.Input;
  224. var canvas:h2d.css.Fill;
  225. var color:Int = 0xFFFFFFFF;
  226. var interact : h2d.Interactive;
  227. public function new(gradient,ix, iy, parent) {
  228. super(parent);
  229. this.gradient = gradient;
  230. x = ix;
  231. y = iy;
  232. init();
  233. }
  234. public function update() {
  235. if(target == null)
  236. return;
  237. locInput.value = Std.string(Math.floor(target.coeff * 100 * 100) / 100);
  238. }
  239. function set_target(cursor:Cursor) {
  240. target = cursor;
  241. locInput.value = Std.string(Math.floor(target.coeff * 100 * 100) / 100);
  242. color = target.value;
  243. colorInput.value = StringTools.hex(color, 8).substr(2);
  244. redraw();
  245. return target;
  246. }
  247. function init() {
  248. title = new h2d.comp.Label("Color #", this);
  249. title.setStyle(Style.get(CLabel));
  250. canvas = new Fill(this);
  251. canvas.x = 45;
  252. canvas.y = -8;
  253. interact = new h2d.Interactive(110, 25, this);
  254. interact.x = 50;
  255. interact.y = -8;
  256. interact.onPush = function(e) {
  257. if(target == null)
  258. return;
  259. if(!gradient.colorPicker.visible) {
  260. gradient.colorPicker.visible = true;
  261. gradient.colorPicker.color = color;
  262. gradient.colorPicker.onChange = function(v) {
  263. color = target.value = v;
  264. colorInput.value = StringTools.hex(v, 8).substr(2);
  265. redraw();
  266. }
  267. }
  268. else {
  269. gradient.colorPicker.onChange = function(v) { };
  270. gradient.colorPicker.visible = false;
  271. }
  272. };
  273. colorInput = new h2d.comp.Input(this);
  274. colorInput.setStyle(Style.get(CInput));
  275. colorInput.x = 175;
  276. colorInput.value = "FFFFFF";
  277. colorInput.onChange = function (e) {
  278. colorInput.value = colorInput.value.toUpperCase();
  279. if(colorInput.value.length > 6) {
  280. colorInput.value = colorInput.value.substr(0, 6);
  281. return;
  282. }
  283. var v = Std.parseInt("0x" + colorInput.value);
  284. if (v != null) {
  285. color = target.value = 255 << 24 | v;
  286. redraw();
  287. }
  288. };
  289. locLabel = new h2d.comp.Label("Location %", this);
  290. locLabel.setStyle(Style.get(CLabel));
  291. locLabel.x = 265;
  292. locInput = new h2d.comp.Input(this);
  293. locInput.setStyle(Style.get(CInput));
  294. locInput.x = 330;
  295. locInput.value = "100.00";
  296. locInput.onChange = function(e) {
  297. var v = Std.parseFloat(locInput.value);
  298. if (!Math.isNaN(v)) {
  299. v = Math.min(100, v);
  300. locInput.value = Std.string(Math.floor(v * 100) / 100);
  301. target.setCursor( v * gradient.boxWidth / 100 );
  302. }
  303. };
  304. redraw();
  305. }
  306. function redraw() {
  307. canvas.reset();
  308. canvas.fillRectColor(0, 0, 110, 25, color);
  309. canvas.lineRect(FillStyle.Color(gradient.borderColor), 0, 0, 110, 25, 1);
  310. }
  311. }
  312. //////////////////////////////////////////////////////////
  313. class GradientEditor extends h2d.comp.Component {
  314. public var borderColor = 0xFF888888;
  315. public var dragTarget:Cursor;
  316. public var dragOut:Bool;
  317. public var updateTarget:Cursor;
  318. public var boxWidth = 430;
  319. var boxHeight = 100;
  320. var keys:Array<Key>;
  321. var colorsKeys:Array<Cursor>;
  322. var alphaKeys:Array<Cursor>;
  323. var box:h2d.Sprite;
  324. var gradient:Fill;
  325. var hudAlpha: AlphaSelector;
  326. var hudColor: ColorSelector;
  327. public var colorPicker:ColorPicker;
  328. var interactUp:h2d.Interactive;
  329. var interactDown:h2d.Interactive;
  330. var withAlpha : Bool;
  331. var holdCursor:Cursor;
  332. public function new(?withAlpha = true, ?parent) {
  333. super("gradienteditor", parent);
  334. this.withAlpha = withAlpha;
  335. init();
  336. }
  337. function init() {
  338. colorsKeys = [];
  339. alphaKeys = [];
  340. interactUp = new h2d.Interactive(boxWidth, 16, this);
  341. interactUp.x = 10;
  342. interactUp.y = 30 - 16;
  343. interactUp.onPush = function(e) createAlphaKey(e.relX, 0);
  344. interactDown = new h2d.Interactive(boxWidth, 16, this);
  345. interactDown.x = 10;
  346. interactDown.y = 30 + boxHeight;
  347. interactDown.onPush = function(e) createColorKey(e.relX, boxHeight);
  348. box = new h2d.Sprite(this);
  349. box.x = 10;
  350. box.y = 30;
  351. drawChecker();
  352. hudColor = new ColorSelector(this, 20, box.y + boxHeight + 30, this);
  353. hudAlpha = new AlphaSelector(this, 20, box.y + boxHeight + 30, this);
  354. hudAlpha.visible = false;
  355. hudAlpha.y = 0;
  356. colorPicker = new ColorPicker(this);
  357. colorPicker.y = 220;
  358. colorPicker.visible = false;
  359. colorPicker.onClose = function() colorPicker.visible = false;
  360. setKeys([ { x : 0, value:0xFFFFFFFF }, { x: 1, value:0xFFFFFFFF } ],null);
  361. }
  362. public dynamic function onChange( keys : Array<Key> ) {
  363. }
  364. public function setKeys(keys:Array<Key>,?kalpha:Array<Key>) {
  365. while( colorsKeys.length > 0 )
  366. colorsKeys.shift().remove();
  367. while( alphaKeys.length > 0 )
  368. alphaKeys.shift().remove();
  369. for( k in keys ) {
  370. var c = new Cursor(this, k.x * boxWidth, boxHeight, KColor, k.value | 0xFF000000, Math.PI, box);
  371. colorsKeys.push(c);
  372. }
  373. if( withAlpha ) {
  374. for( a in (kalpha == null ? keys : kalpha) ) {
  375. var c = new Cursor(this, a.x * boxWidth, 0, KAlpha, a.value >>> 24, 0, box);
  376. alphaKeys.push(c);
  377. }
  378. }
  379. updateKeys();
  380. updateTarget = colorsKeys[0];
  381. }
  382. override function sync(ctx) {
  383. if(dragTarget != null) {
  384. if(dragOut) {
  385. switch(dragTarget.kind) {
  386. case KColor: if(colorsKeys.length > 1) {
  387. colorsKeys.remove(dragTarget);
  388. holdCursor = dragTarget;
  389. dragTarget.visible = false;
  390. }
  391. case KAlpha: if(alphaKeys.length > 1) {
  392. alphaKeys.remove(dragTarget);
  393. holdCursor = dragTarget;
  394. dragTarget.visible = false;
  395. }
  396. }
  397. }
  398. else if(holdCursor == dragTarget) {
  399. holdCursor = null;
  400. dragTarget.visible = true;
  401. switch(dragTarget.kind) {
  402. case KColor: colorsKeys.push(dragTarget);
  403. case KAlpha: alphaKeys.push(dragTarget);
  404. }
  405. }
  406. hudAlpha.update();
  407. hudColor.update();
  408. }
  409. else holdCursor = null;
  410. if(updateTarget != null) {
  411. changeHud(updateTarget);
  412. updateFlags(updateTarget);
  413. updateTarget = null;
  414. }
  415. updateKeys();
  416. super.sync(ctx);
  417. }
  418. function createAlphaKey(px:Float, py:Float) {
  419. if( !withAlpha )
  420. return;
  421. var cursor = new Cursor(this, px, py, KAlpha, getAlphaAt(px / boxWidth), 0, box);
  422. alphaKeys.push(cursor);
  423. updateTarget = cursor;
  424. cursor.drag();
  425. }
  426. function createColorKey(px:Float, py:Float) {
  427. var cursor = new Cursor(this, px, py, KColor, getColorAt(px / boxWidth), Math.PI, box);
  428. colorsKeys.push(cursor);
  429. updateTarget = cursor;
  430. cursor.drag();
  431. }
  432. function updateKeys() {
  433. var keys = [];
  434. for (i in 0...colorsKeys.length) {
  435. var k = colorsKeys[i];
  436. var alpha = getAlphaAt(k.coeff);
  437. var rgb = INTtoRGB(k.value);
  438. keys.push( { x:k.coeff, value:RGBtoINT(rgb[0], rgb[1], rgb[2], alpha) } );
  439. }
  440. for (i in 0...alphaKeys.length) {
  441. var k = alphaKeys[i];
  442. var alpha = k.value;
  443. var rgb = INTtoRGB(getColorAt(k.coeff));
  444. keys.push( { x:k.coeff, value:RGBtoINT(rgb[0], rgb[1], rgb[2], alpha) } );
  445. }
  446. keys.sort(function(a, b) return Reflect.compare(a.x, b.x) );
  447. if(keys[0].x != 0)
  448. keys.insert(0, { x:0, value:keys[0].value } );
  449. if(keys[keys.length - 1].x != 1)
  450. keys.push( { x:1, value:keys[keys.length - 1].value } );
  451. if( Std.string(keys) != Std.string(this.keys) ) {
  452. this.keys = keys;
  453. drawGradient();
  454. onChange(keys);
  455. }
  456. }
  457. function getARGBAt(x:Float) {
  458. var alpha = getAlphaAt(x);
  459. var rgb = INTtoRGB(getColorAt(x));
  460. return RGBtoINT(rgb[0], rgb[1], rgb[2], alpha);
  461. }
  462. function getAlphaAt(x:Float) {
  463. if( !withAlpha )
  464. return 255;
  465. alphaKeys.sort(function(a, b) return Reflect.compare(a.coeff, b.coeff) );
  466. var prev = null;
  467. var next = null;
  468. for (i in 0...alphaKeys.length) {
  469. var k = alphaKeys[i];
  470. if (k.coeff == x)
  471. return k.value;
  472. else if (k.coeff < x)
  473. prev = k;
  474. else if (k.coeff > x) {
  475. if(prev == null)
  476. return k.value;
  477. else next = k;
  478. break;
  479. }
  480. }
  481. if(next == null)
  482. return prev.value;
  483. var d = (x - prev.coeff) / (next.coeff - prev.coeff);
  484. return Math.round(prev.value + (next.value - prev.value) * d);
  485. }
  486. function getColorAt(x:Float) {
  487. colorsKeys.sort(function(a, b) return Reflect.compare(a.coeff, b.coeff) );
  488. var prev = null;
  489. var next = null;
  490. for (i in 0...colorsKeys.length) {
  491. var k = colorsKeys[i];
  492. if (k.coeff == x)
  493. return k.value;
  494. else if (k.coeff < x)
  495. prev = k;
  496. else if (k.coeff > x) {
  497. if(prev == null)
  498. return k.value;
  499. else next = k;
  500. break;
  501. }
  502. }
  503. if(next == null)
  504. return prev.value;
  505. var d = (x - prev.coeff) / (next.coeff - prev.coeff);
  506. var pRGB = INTtoRGB(prev.value);
  507. var nRGB = INTtoRGB(next.value);
  508. var rgb = [];
  509. for (i in 0...3)
  510. rgb.push(Math.round(pRGB[i] + (nRGB[i] - pRGB[i]) * d));
  511. return RGBtoINT(rgb[0], rgb[1], rgb[2]);
  512. }
  513. function drawGradient() {
  514. box.removeChild(gradient);
  515. gradient = new Fill(box);
  516. for (i in 0...keys.length - 1) {
  517. var c1 = keys[i];
  518. var c2 = keys[i + 1];
  519. gradient.fillRectGradient(boxWidth * c1.x, 0, boxWidth * (c2.x - c1.x), boxHeight, c1.value, c2.value, c1.value, c2.value);
  520. }
  521. gradient.lineRect(FillStyle.Color(borderColor), 0, 0, boxWidth, boxHeight, 2);
  522. }
  523. function drawChecker() {
  524. var checker = new Fill(box);
  525. var nb = 90;
  526. var size = Math.ceil(boxWidth / nb);
  527. for (i in 0...nb) {
  528. for (j in 0...nb) {
  529. if(i * size >= boxWidth) break;
  530. if(j * size >= boxHeight) break;
  531. var color = ((i + j) % 2 == 0) ? 0xFFFFFFFF:0xFFAAAAAA;
  532. checker.fillRect(FillStyle.Color(color), i * size, j * size, size, size);
  533. }
  534. }
  535. }
  536. function changeHud(cursor:Cursor) {
  537. switch(cursor.kind) {
  538. case KAlpha: hudAlpha.target = cursor;
  539. hudAlpha.visible = true;
  540. hudAlpha.y = box.y + boxHeight + 30;
  541. hudColor.visible = false;
  542. case KColor: hudColor.target = cursor;
  543. hudColor.visible = true;
  544. hudAlpha.visible = false;
  545. hudAlpha.y = 0;
  546. }
  547. }
  548. function updateFlags(cursor:Cursor) {
  549. for (c in alphaKeys) {
  550. if (c == cursor)
  551. c.select();
  552. else c.unselect();
  553. }
  554. for (c in colorsKeys) {
  555. if (c == cursor)
  556. c.select();
  557. else c.unselect();
  558. }
  559. }
  560. inline public static function INTtoRGB(color:Int) {
  561. return [(color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF, color >>> 24];
  562. }
  563. inline public static function RGBtoINT(r:Int, g:Int, b:Int, a:Int = 255) {
  564. return (a << 24) | (r << 16) | (g << 8) | b;
  565. }
  566. }