From 9c665a011158efd65b2e16f51756a2393a57bafd Mon Sep 17 00:00:00 2001 From: Raykosan <71210053+Raykosan@users.noreply.github.com> Date: Sun, 21 Jun 2026 22:45:35 +0300 Subject: [PATCH] Modification of the RS Color Picker node --- Rayko_Color_Picker.py | 39 +++- __init__.py | 2 +- package.json | 2 +- pyproject.toml | 2 +- web/nano.min.css | 2 + web/pickr.min.js | 3 + web/rs_colorpicker.js | 415 ++++++++++++++++++++++++++++++++++++------ 7 files changed, 400 insertions(+), 65 deletions(-) create mode 100644 web/nano.min.css create mode 100644 web/pickr.min.js diff --git a/Rayko_Color_Picker.py b/Rayko_Color_Picker.py index b377217..5550f50 100644 --- a/Rayko_Color_Picker.py +++ b/Rayko_Color_Picker.py @@ -18,18 +18,37 @@ class RSColorPicker: def INPUT_TYPES(cls): return { "required": { - "color": ("STRING", {"default": "#ff0000", "multiline": False, "tooltip": "Hex color value", "hidden": True}), + "color": ("STRING", {"default": "#ff0000"}), }, } - - RETURN_TYPES = () - FUNCTION = "noop" - OUTPUT_NODE = True + + RETURN_TYPES = ("INT", "STRING", "STRING") + RETURN_NAMES = ("HEX_INT", "HEX_STR", "RGB") + FUNCTION = "get_color" CATEGORY = "🦊 RaykoStudio" - DESCRIPTION = "Interactive color picker widget with pipette support" + + def get_color(self, color): + hex_value = color.lstrip('#') + if len(hex_value) > 6: + hex_value = hex_value[:6] + elif len(hex_value) < 6: + hex_value = hex_value.ljust(6, '0') + + int_value = int(hex_value, 16) + hex_str = '#' + hex_value.upper() + + r = int(hex_value[0:2], 16) / 255.0 + g = int(hex_value[2:4], 16) / 255.0 + b = int(hex_value[4:6], 16) / 255.0 + + rgb_str = f"{r:.3f}, {g:.3f}, {b:.3f}" + + return (int_value, hex_str, rgb_str) - def noop(self, color): - return () +NODE_CLASS_MAPPINGS = { + "RSColorPicker": RSColorPicker +} -NODE_CLASS_MAPPINGS = {"RSColorPicker": RSColorPicker} -NODE_DISPLAY_NAME_MAPPINGS = {"RSColorPicker": "🦊 RS Color Picker"} \ No newline at end of file +NODE_DISPLAY_NAME_MAPPINGS = { + "RSColorPicker": "🦊 RS Color Picker" +} \ No newline at end of file diff --git a/__init__.py b/__init__.py index 570358e..83e6c89 100644 --- a/__init__.py +++ b/__init__.py @@ -29,7 +29,7 @@ IGNORED_MODULES: Set[str] = {'__init__.py'} REQUIRED_MODULES: Set[str] = set() PACKAGE_DISPLAY_NAME = "ComfyUI_RaykoStudio" -__version__ = "0.24.0" +__version__ = "0.25.0" def load_modules(): print(f"🦊 {PACKAGE_DISPLAY_NAME}: Version: {__version__}") diff --git a/package.json b/package.json index 069847d..d50e2ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ComfyUI_RaykoStudio", - "version": "0.24.0", + "version": "0.25.0", "description": "Set of custom nodes for ComfyUI providing additional image processing capabilities", "author": "Raykosan", "license": "Apache License 2.0", diff --git a/pyproject.toml b/pyproject.toml index 4db9e52..7552beb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "ComfyUI_RaykoStudio" description = "Set of custom nodes for ComfyUI providing additional image processing capabilities" -version = "0.24.0" +version = "0.25.0" license = { file = "LICENSE" } dependencies = ["torch>=1.7.0", "numpy>=1.19.0", "Pillow>=8.0.0", "freetype-py>=2.5.1"] diff --git a/web/nano.min.css b/web/nano.min.css new file mode 100644 index 0000000..31ed391 --- /dev/null +++ b/web/nano.min.css @@ -0,0 +1,2 @@ +/*! Pickr 1.9.1 MIT | https://github.com/Simonwep/pickr */ +.pickr{position:relative;overflow:visible;transform:translateY(0)}.pickr *{box-sizing:border-box;outline:none;border:none;-webkit-appearance:none}.pickr .pcr-button{position:relative;height:2em;width:2em;padding:.5em;cursor:pointer;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Helvetica Neue",Arial,sans-serif;border-radius:.15em;background:url("data:image/svg+xml;utf8, ") no-repeat center;background-size:0;transition:all .3s}.pickr .pcr-button::before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url("data:image/svg+xml;utf8, ");background-size:.5em;border-radius:.15em;z-index:-1}.pickr .pcr-button::before{z-index:initial}.pickr .pcr-button::after{position:absolute;content:"";top:0;left:0;height:100%;width:100%;transition:background .3s;background:var(--pcr-color);border-radius:.15em}.pickr .pcr-button.clear{background-size:70%}.pickr .pcr-button.clear::before{opacity:0}.pickr .pcr-button.clear:focus{box-shadow:0 0 0 1px rgba(255,255,255,.85),0 0 0 3px var(--pcr-color)}.pickr .pcr-button.disabled{cursor:not-allowed}.pickr *,.pcr-app *{box-sizing:border-box;outline:none;border:none;-webkit-appearance:none}.pickr input:focus,.pickr input.pcr-active,.pickr button:focus,.pickr button.pcr-active,.pcr-app input:focus,.pcr-app input.pcr-active,.pcr-app button:focus,.pcr-app button.pcr-active{box-shadow:0 0 0 1px rgba(255,255,255,.85),0 0 0 3px var(--pcr-color)}.pickr .pcr-palette,.pickr .pcr-slider,.pcr-app .pcr-palette,.pcr-app .pcr-slider{transition:box-shadow .3s}.pickr .pcr-palette:focus,.pickr .pcr-slider:focus,.pcr-app .pcr-palette:focus,.pcr-app .pcr-slider:focus{box-shadow:0 0 0 1px rgba(255,255,255,.85),0 0 0 3px rgba(0,0,0,.25)}.pcr-app{position:fixed;display:flex;flex-direction:column;z-index:10000;border-radius:.1em;background:#fff;opacity:0;visibility:hidden;transition:opacity .3s,visibility 0s .3s;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Helvetica Neue",Arial,sans-serif;box-shadow:0 .15em 1.5em 0 rgba(0,0,0,.1),0 0 1em 0 rgba(0,0,0,.03);left:0;top:0}.pcr-app.visible{transition:opacity .3s;visibility:visible;opacity:1}.pcr-app .pcr-swatches{display:flex;flex-wrap:wrap;margin-top:.75em}.pcr-app .pcr-swatches.pcr-last{margin:0}@supports(display: grid){.pcr-app .pcr-swatches{display:grid;align-items:center;grid-template-columns:repeat(auto-fit, 1.75em)}}.pcr-app .pcr-swatches>button{font-size:1em;position:relative;width:calc(1.75em - 5px);height:calc(1.75em - 5px);border-radius:.15em;cursor:pointer;margin:2.5px;flex-shrink:0;justify-self:center;transition:all .15s;overflow:hidden;background:rgba(0,0,0,0);z-index:1}.pcr-app .pcr-swatches>button::before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url("data:image/svg+xml;utf8, ");background-size:6px;border-radius:.15em;z-index:-1}.pcr-app .pcr-swatches>button::after{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background:var(--pcr-color);border:1px solid rgba(0,0,0,.05);border-radius:.15em;box-sizing:border-box}.pcr-app .pcr-swatches>button:hover{filter:brightness(1.05)}.pcr-app .pcr-swatches>button:not(.pcr-active){box-shadow:none}.pcr-app .pcr-interaction{display:flex;flex-wrap:wrap;align-items:center;margin:0 -0.2em 0 -0.2em}.pcr-app .pcr-interaction>*{margin:0 .2em}.pcr-app .pcr-interaction input{letter-spacing:.07em;font-size:.75em;text-align:center;cursor:pointer;color:#75797e;background:#f1f3f4;border-radius:.15em;transition:all .15s;padding:.45em .5em;margin-top:.75em}.pcr-app .pcr-interaction input:hover{filter:brightness(0.975)}.pcr-app .pcr-interaction input:focus{box-shadow:0 0 0 1px rgba(255,255,255,.85),0 0 0 3px rgba(66,133,244,.75)}.pcr-app .pcr-interaction .pcr-result{color:#75797e;text-align:left;flex:1 1 8em;min-width:8em;transition:all .2s;border-radius:.15em;background:#f1f3f4;cursor:text}.pcr-app .pcr-interaction .pcr-result::-moz-selection{background:#4285f4;color:#fff}.pcr-app .pcr-interaction .pcr-result::selection{background:#4285f4;color:#fff}.pcr-app .pcr-interaction .pcr-type.active{color:#fff;background:#4285f4}.pcr-app .pcr-interaction .pcr-save,.pcr-app .pcr-interaction .pcr-cancel,.pcr-app .pcr-interaction .pcr-clear{color:#fff;width:auto}.pcr-app .pcr-interaction .pcr-save,.pcr-app .pcr-interaction .pcr-cancel,.pcr-app .pcr-interaction .pcr-clear{color:#fff}.pcr-app .pcr-interaction .pcr-save:hover,.pcr-app .pcr-interaction .pcr-cancel:hover,.pcr-app .pcr-interaction .pcr-clear:hover{filter:brightness(0.925)}.pcr-app .pcr-interaction .pcr-save{background:#4285f4}.pcr-app .pcr-interaction .pcr-clear,.pcr-app .pcr-interaction .pcr-cancel{background:#f44250}.pcr-app .pcr-interaction .pcr-clear:focus,.pcr-app .pcr-interaction .pcr-cancel:focus{box-shadow:0 0 0 1px rgba(255,255,255,.85),0 0 0 3px rgba(244,66,80,.75)}.pcr-app .pcr-selection .pcr-picker{position:absolute;height:18px;width:18px;border:2px solid #fff;border-radius:100%;-webkit-user-select:none;-moz-user-select:none;user-select:none}.pcr-app .pcr-selection .pcr-color-palette,.pcr-app .pcr-selection .pcr-color-chooser,.pcr-app .pcr-selection .pcr-color-opacity{position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;display:flex;flex-direction:column;cursor:grab;cursor:-webkit-grab}.pcr-app .pcr-selection .pcr-color-palette:active,.pcr-app .pcr-selection .pcr-color-chooser:active,.pcr-app .pcr-selection .pcr-color-opacity:active{cursor:grabbing;cursor:-webkit-grabbing}.pcr-app[data-theme=nano]{width:14.25em;max-width:95vw}.pcr-app[data-theme=nano] .pcr-swatches{margin-top:.6em;padding:0 .6em}.pcr-app[data-theme=nano] .pcr-interaction{padding:0 .6em .6em .6em}.pcr-app[data-theme=nano] .pcr-selection{display:grid;grid-gap:.6em;grid-template-columns:1fr 4fr;grid-template-rows:5fr auto auto;align-items:center;height:10.5em;width:100%;align-self:flex-start}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview{grid-area:2/1/4/1;height:100%;width:100%;display:flex;flex-direction:row;justify-content:center;margin-left:.6em}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview .pcr-last-color{display:none}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview .pcr-current-color{position:relative;background:var(--pcr-color);width:2em;height:2em;border-radius:50em;overflow:hidden}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview .pcr-current-color::before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url("data:image/svg+xml;utf8, ");background-size:.5em;border-radius:.15em;z-index:-1}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-palette{grid-area:1/1/2/3;width:100%;height:100%;z-index:1}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-palette .pcr-palette{border-radius:.15em;width:100%;height:100%}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-palette .pcr-palette::before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url("data:image/svg+xml;utf8, ");background-size:.5em;border-radius:.15em;z-index:-1}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser{grid-area:2/2/2/2}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity{grid-area:3/2/3/2}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser,.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity{height:.5em;margin:0 .6em}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser .pcr-picker,.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity .pcr-picker{top:50%;transform:translateY(-50%)}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser .pcr-slider,.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity .pcr-slider{flex-grow:1;border-radius:50em}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser .pcr-slider{background:linear-gradient(to right, hsl(0, 100%, 50%), hsl(60, 100%, 50%), hsl(120, 100%, 50%), hsl(180, 100%, 50%), hsl(240, 100%, 50%), hsl(300, 100%, 50%), hsl(0, 100%, 50%))}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity .pcr-slider{background:linear-gradient(to right, transparent, black),url("data:image/svg+xml;utf8, ");background-size:100%,.25em} diff --git a/web/pickr.min.js b/web/pickr.min.js new file mode 100644 index 0000000..c175e36 --- /dev/null +++ b/web/pickr.min.js @@ -0,0 +1,3 @@ +/*! Pickr 1.9.1 MIT | https://github.com/Simonwep/pickr */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Pickr=e():t.Pickr=e()}(self,(()=>(()=>{"use strict";var t={d:(e,o)=>{for(var n in o)t.o(o,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:o[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.d(e,{default:()=>E});var o={};function n(t,e,o,n,i={}){e instanceof HTMLCollection||e instanceof NodeList?e=Array.from(e):Array.isArray(e)||(e=[e]),Array.isArray(o)||(o=[o]);for(const s of e)for(const e of o)s[t](e,n,{capture:!1,...i});return Array.prototype.slice.call(arguments,1)}t.r(o),t.d(o,{adjustableInputNumbers:()=>p,createElementFromString:()=>r,createFromTemplate:()=>a,eventPath:()=>l,off:()=>s,on:()=>i,resolveElement:()=>c});const i=n.bind(null,"addEventListener"),s=n.bind(null,"removeEventListener");function r(t){const e=document.createElement("div");return e.innerHTML=t.trim(),e.firstElementChild}function a(t){const e=(t,e)=>{const o=t.getAttribute(e);return t.removeAttribute(e),o},o=(t,n={})=>{const i=e(t,":obj"),s=e(t,":ref"),r=i?n[i]={}:n;s&&(n[s]=t);for(const n of Array.from(t.children)){const t=e(n,":arr"),i=o(n,t?{}:r);t&&(r[t]||(r[t]=[])).push(Object.keys(i).length?i:n)}return n};return o(r(t))}function l(t){let e=t.path||t.composedPath&&t.composedPath();if(e)return e;let o=t.target.parentElement;for(e=[t.target,o];o=o.parentElement;)e.push(o);return e.push(document,window),e}function c(t){return t instanceof Element?t:"string"==typeof t?t.split(/>>/g).reduce(((t,e,o,n)=>(t=t.querySelector(e),ot)){function o(o){const n=[.001,.01,.1][Number(o.shiftKey||2*o.ctrlKey)]*(o.deltaY<0?1:-1);let i=0,s=t.selectionStart;t.value=t.value.replace(/[\d.]+/g,((t,o)=>o<=s&&o+t.length>=s?(s=o,e(Number(t),n,i)):(i++,t))),t.focus(),t.setSelectionRange(s,s),o.preventDefault(),t.dispatchEvent(new Event("input"))}i(t,"focus",(()=>i(window,"wheel",o,{passive:!1}))),i(t,"blur",(()=>s(window,"wheel",o)))}const{min:u,max:h,floor:d,round:m}=Math;function f(t,e,o){e/=100,o/=100;const n=d(t=t/360*6),i=t-n,s=o*(1-e),r=o*(1-i*e),a=o*(1-(1-i)*e),l=n%6;return[255*[o,r,s,s,a,o][l],255*[a,o,o,r,s,s][l],255*[s,s,a,o,o,r][l]]}function v(t,e,o){const n=(2-(e/=100))*(o/=100)/2;return 0!==n&&(e=1===n?0:n<.5?e*o/(2*n):e*o/(2-2*n)),[t,100*e,100*n]}function b(t,e,o){const n=u(t/=255,e/=255,o/=255),i=h(t,e,o),s=i-n;let r,a;if(0===s)r=a=0;else{a=s/i;const n=((i-t)/6+s/2)/s,l=((i-e)/6+s/2)/s,c=((i-o)/6+s/2)/s;t===i?r=c-l:e===i?r=1/3+n-c:o===i&&(r=2/3+l-n),r<0?r+=1:r>1&&(r-=1)}return[360*r,100*a,100*i]}function y(t,e,o,n){e/=100,o/=100;return[...b(255*(1-u(1,(t/=100)*(1-(n/=100))+n)),255*(1-u(1,e*(1-n)+n)),255*(1-u(1,o*(1-n)+n)))]}function g(t,e,o){e/=100;const n=2*(e*=(o/=100)<.5?o:1-o)/(o+e)*100,i=100*(o+e);return[t,isNaN(n)?0:n,i]}function _(t){return b(...t.match(/.{2}/g).map((t=>parseInt(t,16))))}function w(t){t=t.match(/^[a-zA-Z]+$/)?function(t){if("black"===t.toLowerCase())return"#000";const e=document.createElement("canvas").getContext("2d");return e.fillStyle=t,"#000"===e.fillStyle?null:e.fillStyle}(t):t;const e={cmyk:/^cmyk\D+([\d.]+)\D+([\d.]+)\D+([\d.]+)\D+([\d.]+)/i,rgba:/^rgba?\D+([\d.]+)(%?)\D+([\d.]+)(%?)\D+([\d.]+)(%?)\D*?(([\d.]+)(%?)|$)/i,hsla:/^hsla?\D+([\d.]+)\D+([\d.]+)\D+([\d.]+)\D*?(([\d.]+)(%?)|$)/i,hsva:/^hsva?\D+([\d.]+)\D+([\d.]+)\D+([\d.]+)\D*?(([\d.]+)(%?)|$)/i,hexa:/^#?(([\dA-Fa-f]{3,4})|([\dA-Fa-f]{6})|([\dA-Fa-f]{8}))$/i},o=t=>t.map((t=>/^(|\d+)\.\d+|\d+$/.test(t)?Number(t):void 0));let n;t:for(const i in e)if(n=e[i].exec(t))switch(i){case"cmyk":{const[,t,e,s,r]=o(n);if(t>100||e>100||s>100||r>100)break t;return{values:y(t,e,s,r),type:i}}case"rgba":{let[,t,,e,,s,,,r]=o(n);if(t="%"===n[2]?t/100*255:t,e="%"===n[4]?e/100*255:e,s="%"===n[6]?s/100*255:s,r="%"===n[9]?r/100:r,t>255||e>255||s>255||r<0||r>1)break t;return{values:[...b(t,e,s),r],a:r,type:i}}case"hexa":{let[,t]=n;4!==t.length&&3!==t.length||(t=t.split("").map((t=>t+t)).join(""));const e=t.substring(0,6);let o=t.substring(6);return o=o?parseInt(o,16)/255:void 0,{values:[..._(e),o],a:o,type:i}}case"hsla":{let[,t,e,s,,r]=o(n);if(r="%"===n[6]?r/100:r,t>360||e>100||s>100||r<0||r>1)break t;return{values:[...g(t,e,s),r],a:r,type:i}}case"hsva":{let[,t,e,s,,r]=o(n);if(r="%"===n[6]?r/100:r,t>360||e>100||s>100||r<0||r>1)break t;return{values:[t,e,s,r],a:r,type:i}}}return{values:null,type:null}}function A(t=0,e=0,o=0,n=1){const i=(t,e)=>(o=-1)=>e(~o?t.map((t=>Number(t.toFixed(o)))):t),s={h:t,s:e,v:o,a:n,toHSVA(){const t=[s.h,s.s,s.v,s.a];return t.toString=i(t,(t=>`hsva(${t[0]}, ${t[1]}%, ${t[2]}%, ${s.a})`)),t},toHSLA(){const t=[...v(s.h,s.s,s.v),s.a];return t.toString=i(t,(t=>`hsla(${t[0]}, ${t[1]}%, ${t[2]}%, ${s.a})`)),t},toRGBA(){const t=[...f(s.h,s.s,s.v),s.a];return t.toString=i(t,(t=>`rgba(${t[0]}, ${t[1]}, ${t[2]}, ${s.a})`)),t},toCMYK(){const t=function(t,e,o){const n=f(t,e,o),i=n[0]/255,s=n[1]/255,r=n[2]/255,a=u(1-i,1-s,1-r);return[100*(1===a?0:(1-i-a)/(1-a)),100*(1===a?0:(1-s-a)/(1-a)),100*(1===a?0:(1-r-a)/(1-a)),100*a]}(s.h,s.s,s.v);return t.toString=i(t,(t=>`cmyk(${t[0]}%, ${t[1]}%, ${t[2]}%, ${t[3]}%)`)),t},toHEXA(){const t=function(t,e,o){return f(t,e,o).map((t=>m(t).toString(16).padStart(2,"0")))}(s.h,s.s,s.v),e=s.a>=1?"":Number((255*s.a).toFixed(0)).toString(16).toUpperCase().padStart(2,"0");return e&&t.push(e),t.toString=()=>`#${t.join("").toUpperCase()}`,t},clone:()=>A(s.h,s.s,s.v,s.a)};return s}const $=t=>Math.max(Math.min(t,1),0);function C(t){const e={options:Object.assign({lock:null,onchange:()=>0,onstop:()=>0},t),_keyboard(t){const{options:o}=e,{type:n,key:i}=t;if(document.activeElement===o.wrapper){const{lock:o}=e.options,s="ArrowUp"===i,r="ArrowRight"===i,a="ArrowDown"===i,l="ArrowLeft"===i;if("keydown"===n&&(s||r||a||l)){let n=0,i=0;"v"===o?n=s||r?1:-1:"h"===o?n=s||r?-1:1:(i=s?-1:a?1:0,n=l?-1:r?1:0),e.update($(e.cache.x+.01*n),$(e.cache.y+.01*i)),t.preventDefault()}else i.startsWith("Arrow")&&(e.options.onstop(),t.preventDefault())}},_tapstart(t){i(document,["mouseup","touchend","touchcancel"],e._tapstop),i(document,["mousemove","touchmove"],e._tapmove),t.cancelable&&t.preventDefault(),e._tapmove(t)},_tapmove(t){const{options:o,cache:n}=e,{lock:i,element:s,wrapper:r}=o,a=r.getBoundingClientRect();let l=0,c=0;if(t){const e=t&&t.touches&&t.touches[0];l=t?(e||t).clientX:0,c=t?(e||t).clientY:0,la.left+a.width&&(l=a.left+a.width),ca.top+a.height&&(c=a.top+a.height),l-=a.left,c-=a.top}else n&&(l=n.x*a.width,c=n.y*a.height);"h"!==i&&(s.style.left=`calc(${l/a.width*100}% - ${s.offsetWidth/2}px)`),"v"!==i&&(s.style.top=`calc(${c/a.height*100}% - ${s.offsetHeight/2}px)`),e.cache={x:l/a.width,y:c/a.height};const p=$(l/a.width),u=$(c/a.height);switch(i){case"v":return o.onchange(p);case"h":return o.onchange(u);default:return o.onchange(p,u)}},_tapstop(){e.options.onstop(),s(document,["mouseup","touchend","touchcancel"],e._tapstop),s(document,["mousemove","touchmove"],e._tapmove)},trigger(){e._tapmove()},update(t=0,o=0){const{left:n,top:i,width:s,height:r}=e.options.wrapper.getBoundingClientRect();"h"===e.options.lock&&(o=t),e._tapmove({clientX:n+s*t,clientY:i+r*o})},destroy(){const{options:t,_tapstart:o,_keyboard:n}=e;s(document,["keydown","keyup"],n),s([t.wrapper,t.element],"mousedown",o),s([t.wrapper,t.element],"touchstart",o,{passive:!1})}},{options:o,_tapstart:n,_keyboard:r}=e;return i([o.wrapper,o.element],"mousedown",n),i([o.wrapper,o.element],"touchstart",n,{passive:!1}),i(document,["keydown","keyup"],r),e}function k(t={}){t=Object.assign({onchange:()=>0,className:"",elements:[]},t);const e=i(t.elements,"click",(e=>{t.elements.forEach((o=>o.classList[e.target===o?"add":"remove"](t.className))),t.onchange(e),e.stopPropagation()}));return{destroy:()=>s(...e)}}const S={variantFlipOrder:{start:"sme",middle:"mse",end:"ems"},positionFlipOrder:{top:"tbrl",right:"rltb",bottom:"btrl",left:"lrbt"},position:"bottom",margin:8,padding:0},O=(t,e,o)=>{const n="object"!=typeof t||t instanceof HTMLElement?{reference:t,popper:e,...o}:t;return{update(t=n){const{reference:e,popper:o}=Object.assign(n,t);if(!o||!e)throw new Error("Popper- or reference-element missing.");return((t,e,o)=>{const{container:n,arrow:i,margin:s,padding:r,position:a,variantFlipOrder:l,positionFlipOrder:c}={container:document.documentElement.getBoundingClientRect(),...S,...o},{left:p,top:u}=e.style;e.style.left="0",e.style.top="0";const h=t.getBoundingClientRect(),d=e.getBoundingClientRect(),m={t:h.top-d.height-s,b:h.bottom+s,r:h.right+s,l:h.left-d.width-s},f={vs:h.left,vm:h.left+h.width/2-d.width/2,ve:h.left+h.width-d.width,hs:h.top,hm:h.bottom-h.height/2-d.height/2,he:h.bottom-d.height},[v,b="middle"]=a.split("-"),y=c[v],g=l[b],{top:_,left:w,bottom:A,right:$}=n;for(const t of y){const o="t"===t||"b"===t;let n=m[t];const[s,a]=o?["top","left"]:["left","top"],[l,c]=o?[d.height,d.width]:[d.width,d.height],[p,u]=o?[A,$]:[$,A],[v,b]=o?[_,w]:[w,_];if(!(np))for(const p of g){let m=f[(o?"v":"h")+p];if(!(mu)){if(m-=d[a],n-=d[s],e.style[a]=`${m}px`,e.style[s]=`${n}px`,i){const e=o?h.width/2:h.height/2,r=c/2,u=e>r,d=m+{s:u?r:e,m:r,e:u?r:c-e}[p],f=n+{t:l,b:0,r:0,l}[t];i.style[a]=`${d}px`,i.style[s]=`${f}px`}return t+p}}}return e.style.left=p,e.style.top=u,null})(e,o,n)}}};class E{static utils=o;static version="1.9.1";static I18N_DEFAULTS={"ui:dialog":"color picker dialog","btn:toggle":"toggle color picker dialog","btn:swatch":"color swatch","btn:last-color":"use previous color","btn:save":"Save","btn:cancel":"Cancel","btn:clear":"Clear","aria:btn:save":"save and close","aria:btn:cancel":"cancel and close","aria:btn:clear":"clear and close","aria:input":"color input field","aria:palette":"color selection area","aria:hue":"hue selection slider","aria:opacity":"selection slider"};static DEFAULT_OPTIONS={appClass:null,theme:"classic",useAsButton:!1,padding:8,disabled:!1,comparison:!0,closeOnScroll:!1,outputPrecision:0,lockOpacity:!1,autoReposition:!0,container:"body",components:{interaction:{}},i18n:{},swatches:null,inline:!1,sliders:null,default:"#42445a",defaultRepresentation:null,position:"bottom-middle",adjustableNumbers:!0,showAlways:!1,closeWithKey:"Escape"};_initializingActive=!0;_recalc=!0;_nanopop=null;_root=null;_color=A();_lastColor=A();_swatchColors=[];_setupAnimationFrame=null;_eventListener={init:[],save:[],hide:[],show:[],clear:[],change:[],changestop:[],cancel:[],swatchselect:[]};constructor(t){this.options=t=Object.assign({...E.DEFAULT_OPTIONS},t);const{swatches:e,components:o,theme:n,sliders:i,lockOpacity:s,padding:r}=t;["nano","monolith"].includes(n)&&!i&&(t.sliders="h"),o.interaction||(o.interaction={});const{preview:a,opacity:l,hue:c,palette:p}=o;o.opacity=!s&&l,o.palette=p||a||l||c,this._preBuild(),this._buildComponents(),this._bindEvents(),this._finalBuild(),e&&e.length&&e.forEach((t=>this.addSwatch(t)));const{button:u,app:h}=this._root;this._nanopop=O(u,h,{margin:r}),u.setAttribute("role","button"),u.setAttribute("aria-label",this._t("btn:toggle"));const d=this;this._setupAnimationFrame=requestAnimationFrame((function e(){if(!h.offsetWidth)return requestAnimationFrame(e);d.setColor(t.default),d._rePositioningPicker(),t.defaultRepresentation&&(d._representation=t.defaultRepresentation,d.setColorRepresentation(d._representation)),t.showAlways&&d.show(),d._initializingActive=!1,d._emit("init")}))}static create=t=>new E(t);_preBuild(){const{options:t}=this;for(const e of["el","container"])t[e]=c(t[e]);this._root=(t=>{const{components:e,useAsButton:o,inline:n,appClass:i,theme:s,lockOpacity:r}=t.options,l=t=>t?"":'style="display:none" hidden',c=e=>t._t(e),p=a(`\n \n\n ${o?"":''}\n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n \n\n \n\n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n `),u=p.interaction;return u.options.find((t=>!t.hidden&&!t.classList.add("active"))),u.type=()=>u.options.find((t=>t.classList.contains("active"))),p})(this),t.useAsButton&&(this._root.button=t.el),t.container.appendChild(this._root.root)}_finalBuild(){const t=this.options,e=this._root;if(t.container.removeChild(e.root),t.inline){const o=t.el.parentElement;t.el.nextSibling?o.insertBefore(e.app,t.el.nextSibling):o.appendChild(e.app)}else t.container.appendChild(e.app);t.useAsButton?t.inline&&t.el.remove():t.el.parentNode.replaceChild(e.root,t.el),t.disabled&&this.disable(),t.comparison||(e.button.style.transition="none",t.useAsButton||(e.preview.lastColor.style.transition="none")),this.hide()}_buildComponents(){const t=this,e=this.options.components,o=(t.options.sliders||"v").repeat(2),[n,i]=o.match(/^[vh]+$/g)?o:[],s=()=>this._color||(this._color=this._lastColor.clone()),r={palette:C({element:t._root.palette.picker,wrapper:t._root.palette.palette,onstop:()=>t._emit("changestop","slider",t),onchange(o,n){if(!e.palette)return;const i=s(),{_root:r,options:a}=t,{lastColor:l,currentColor:c}=r.preview;t._recalc&&(i.s=100*o,i.v=100-100*n,i.v<0&&(i.v=0),t._updateOutput("slider"));const p=i.toRGBA().toString(0);this.element.style.background=p,this.wrapper.style.background=`\n linear-gradient(to top, rgba(0, 0, 0, ${i.a}), transparent),\n linear-gradient(to left, hsla(${i.h}, 100%, 50%, ${i.a}), rgba(255, 255, 255, ${i.a}))\n `,a.comparison?a.useAsButton||t._lastColor||l.style.setProperty("--pcr-color",p):(r.button.style.setProperty("--pcr-color",p),r.button.classList.remove("clear"));const u=i.toHEXA().toString();for(const{el:e,color:o}of t._swatchColors)e.classList[u===o.toHEXA().toString()?"add":"remove"]("pcr-active");c.style.setProperty("--pcr-color",p)}}),hue:C({lock:"v"===i?"h":"v",element:t._root.hue.picker,wrapper:t._root.hue.slider,onstop:()=>t._emit("changestop","slider",t),onchange(o){if(!e.hue||!e.palette)return;const n=s();t._recalc&&(n.h=360*o),this.element.style.backgroundColor=`hsl(${n.h}, 100%, 50%)`,r.palette.trigger()}}),opacity:C({lock:"v"===n?"h":"v",element:t._root.opacity.picker,wrapper:t._root.opacity.slider,onstop:()=>t._emit("changestop","slider",t),onchange(o){if(!e.opacity||!e.palette)return;const n=s();t._recalc&&(n.a=Math.round(100*o)/100),this.element.style.background=`rgba(0, 0, 0, ${n.a})`,r.palette.trigger()}}),selectable:k({elements:t._root.interaction.options,className:"active",onchange(e){t._representation=e.target.getAttribute("data-type").toUpperCase(),t._recalc&&t._updateOutput("swatch")}})};this._components=r}_bindEvents(){const{_root:t,options:e}=this,o=[i(t.interaction.clear,"click",(()=>this._clearColor())),i([t.interaction.cancel,t.preview.lastColor],"click",(()=>{this.setHSVA(...(this._lastColor||this._color).toHSVA(),!0),this._emit("cancel")})),i(t.interaction.save,"click",(()=>{!this.applyColor()&&!e.showAlways&&this.hide()})),i(t.interaction.result,["keyup","input"],(t=>{this.setColor(t.target.value,!0)&&!this._initializingActive&&(this._emit("change",this._color,"input",this),this._emit("changestop","input",this)),t.stopImmediatePropagation()})),i(t.interaction.result,["focus","blur"],(t=>{this._recalc="blur"===t.type,this._recalc&&this._updateOutput(null)})),i([t.palette.palette,t.palette.picker,t.hue.slider,t.hue.picker,t.opacity.slider,t.opacity.picker],["mousedown","touchstart"],(()=>this._recalc=!0),{passive:!0})];if(!e.showAlways){const n=e.closeWithKey;o.push(i(t.button,"click",(()=>this.isOpen()?this.hide():this.show())),i(document,"keyup",(t=>this.isOpen()&&(t.key===n||t.code===n)&&this.hide())),i(document,["touchstart","mousedown"],(e=>{this.isOpen()&&!l(e).some((e=>e===t.app||e===t.button))&&this.hide()}),{capture:!0}))}if(e.adjustableNumbers){const e={rgba:[255,255,255,1],hsva:[360,100,100,1],hsla:[360,100,100,1],cmyk:[100,100,100,100]};p(t.interaction.result,((t,o,n)=>{const i=e[this.getColorRepresentation().toLowerCase()];if(i){const e=i[n],s=t+(e>=100?1e3*o:o);return s<=0?0:Number((s{n.isOpen()&&(e.closeOnScroll&&n.hide(),null===t?(t=setTimeout((()=>t=null),100),requestAnimationFrame((function e(){n._rePositioningPicker(),null!==t&&requestAnimationFrame(e)}))):(clearTimeout(t),t=setTimeout((()=>t=null),100)))}),{capture:!0}))}this._eventBindings=o}_rePositioningPicker(){const{options:t}=this;if(!t.inline){if(!this._nanopop.update({container:document.body.getBoundingClientRect(),position:t.position})){const t=this._root.app,e=t.getBoundingClientRect();t.style.top=(window.innerHeight-e.height)/2+"px",t.style.left=(window.innerWidth-e.width)/2+"px"}}}_updateOutput(t){const{_root:e,_color:o,options:n}=this;if(e.interaction.type()){const t=`to${e.interaction.type().getAttribute("data-type")}`;e.interaction.result.value="function"==typeof o[t]?o[t]().toString(n.outputPrecision):""}!this._initializingActive&&this._recalc&&this._emit("change",o,t,this)}_clearColor(t=!1){const{_root:e,options:o}=this;o.useAsButton||e.button.style.setProperty("--pcr-color","rgba(0, 0, 0, 0.15)"),e.button.classList.add("clear"),o.showAlways||this.hide(),this._lastColor=null,this._initializingActive||t||(this._emit("save",null),this._emit("clear"))}_parseLocalColor(t){const{values:e,type:o,a:n}=w(t),{lockOpacity:i}=this.options,s=void 0!==n&&1!==n;return e&&3===e.length&&(e[3]=void 0),{values:!e||i&&s?null:e,type:o}}_t(t){return this.options.i18n[t]||E.I18N_DEFAULTS[t]}_emit(t,...e){this._eventListener[t].forEach((t=>t(...e,this)))}on(t,e){return this._eventListener[t].push(e),this}off(t,e){const o=this._eventListener[t]||[],n=o.indexOf(e);return~n&&o.splice(n,1),this}addSwatch(t){const{values:e}=this._parseLocalColor(t);if(e){const{_swatchColors:t,_root:o}=this,n=A(...e),s=r(``);return o.swatches.appendChild(s),t.push({el:s,color:n}),this._eventBindings.push(i(s,"click",(()=>{this.setHSVA(...n.toHSVA(),!0),this._emit("swatchselect",n),this._emit("change",n,"swatch",this)}))),!0}return!1}removeSwatch(t){const e=this._swatchColors[t];if(e){const{el:o}=e;return this._root.swatches.removeChild(o),this._swatchColors.splice(t,1),!0}return!1}applyColor(t=!1){const{preview:e,button:o}=this._root,n=this._color.toRGBA().toString(0);return e.lastColor.style.setProperty("--pcr-color",n),this.options.useAsButton||o.style.setProperty("--pcr-color",n),o.classList.remove("clear"),this._lastColor=this._color.clone(),this._initializingActive||t||this._emit("save",this._color),this}destroy(){cancelAnimationFrame(this._setupAnimationFrame),this._eventBindings.forEach((t=>s(...t))),Object.keys(this._components).forEach((t=>this._components[t].destroy()))}destroyAndRemove(){this.destroy();const{root:t,app:e}=this._root;t.parentElement&&t.parentElement.removeChild(t),e.parentElement.removeChild(e),Object.keys(this).forEach((t=>this[t]=null))}hide(){return!!this.isOpen()&&(this._root.app.classList.remove("visible"),this._emit("hide"),!0)}show(){return!this.options.disabled&&!this.isOpen()&&(this._root.app.classList.add("visible"),this._rePositioningPicker(),this._emit("show",this._color),this)}isOpen(){return this._root.app.classList.contains("visible")}setHSVA(t=360,e=0,o=0,n=1,i=!1){const s=this._recalc;if(this._recalc=!1,t<0||t>360||e<0||e>100||o<0||o>100||n<0||n>1)return!1;this._color=A(t,e,o,n);const{hue:r,opacity:a,palette:l}=this._components;return r.update(t/360),a.update(n),l.update(e/100,1-o/100),i||this.applyColor(),s&&this._updateOutput(),this._recalc=s,!0}setColor(t,e=!1){if(null===t)return this._clearColor(e),!0;const{values:o,type:n}=this._parseLocalColor(t);if(o){const t=n.toUpperCase(),{options:i}=this._root.interaction,s=i.find((e=>e.getAttribute("data-type")===t));if(s&&!s.hidden)for(const t of i)t.classList[t===s?"add":"remove"]("active");return!!this.setHSVA(...o,e)&&this.setColorRepresentation(t)}return!1}setColorRepresentation(t){return t=t.toUpperCase(),!!this._root.interaction.options.find((e=>e.getAttribute("data-type").startsWith(t)&&!e.click()))}getColorRepresentation(){return this._representation}getColor(){return this._color}getSelectedColor(){return this._lastColor}getRoot(){return this._root}disable(){return this.hide(),this.options.disabled=!0,this._root.button.classList.add("disabled"),this}enable(){return this.options.disabled=!1,this._root.button.classList.remove("disabled"),this}}return e=e.default})())); +//# sourceMappingURL=pickr.min.js.map \ No newline at end of file diff --git a/web/rs_colorpicker.js b/web/rs_colorpicker.js index c2202af..682a622 100644 --- a/web/rs_colorpicker.js +++ b/web/rs_colorpicker.js @@ -2,9 +2,11 @@ console.log("[SPLINE 🦊] rs_colorpicker.js LOADED!"); import { app } from "../../scripts/app.js"; const NODE_CLASS = "RSColorPicker"; -const WIDGET_W = 150; -const WIDGET_H = 32; -const NODE_CHROME_H = 32; // ComfyUI title bar + padding overhead +const NODE_W = 200; +const NODE_H = 260; +const NODE_CHROME_H = 32; +const PRESETS = ["#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#ffffff", "#000000"]; +const HISTORY_KEY = "rs_colorpicker_history"; function mkEl(tag, css, extra) { const el = document.createElement(tag); @@ -13,84 +15,393 @@ function mkEl(tag, css, extra) { return el; } -function buildUI(defaultColor) { - const root = mkEl("div", "width:100%;height:100%;display:flex;align-items:center;gap:6px;padding:3px 6px;box-sizing:border-box;font-family:system-ui,sans-serif;"); - - const swatch = mkEl("div", `width:26px;height:26px;border-radius:4px;border:1.5px solid #444;cursor:pointer;background:${defaultColor};flex-shrink:0;transition:border-color 0.1s;`); - swatch.onmouseenter = () => { swatch.style.borderColor = "#6688aa"; }; - swatch.onmouseleave = () => { swatch.style.borderColor = "#444"; }; - - const colorPicker = mkEl("input", "width:0;height:0;opacity:0;position:absolute;pointer-events:none;", { type: "color", value: defaultColor }); - - const hexInput = mkEl("input", "width:74px;padding:4px 5px;font-size:12px;font-family:monospace;background:#1e1e1e;color:#ccc;border:1px solid #444;border-radius:4px;text-align:center;outline:none;", { value: defaultColor.toUpperCase() }); - hexInput.addEventListener("focus", () => { hexInput.style.borderColor = "#5090cc"; }); - hexInput.addEventListener("blur", () => { hexInput.style.borderColor = "#444"; }); - - swatch.onclick = () => colorPicker.click(); - - root.append(swatch, colorPicker, hexInput); - return { root, swatch, colorPicker, hexInput }; +function hexToInt(hex) { + return parseInt(hex.replace('#', '').slice(0, 6), 16); } +function rgbToHex(r, g, b) { + return '#' + [r, g, b].map(x => Math.round(x).toString(16).padStart(2, '0')).join('').toUpperCase(); +} + +function hexToRGB(hex) { + let h = hex.replace('#', ''); + if (h.length === 3) h = h.split('').map(c => c + c).join(''); + else if (h.length > 6) h = h.slice(0, 6); + return { + r: parseInt(h.slice(0, 2), 16) / 255, + g: parseInt(h.slice(2, 4), 16) / 255, + b: parseInt(h.slice(4, 6), 16) / 255 + }; +} + +function normalizeHex(hex) { + let h = hex.replace('#', ''); + if (h.length === 3) h = h.split('').map(c => c + c).join(''); + else if (h.length > 6) h = h.slice(0, 6); + else if (h.length < 6) h = h.padEnd(6, '0'); + return '#' + h; +} + +function getHistory() { + try { return JSON.parse(localStorage.getItem(HISTORY_KEY)) || []; } + catch { return []; } +} + +function addToHistory(color) { + const history = getHistory().filter(c => c !== color); + history.unshift(color); + localStorage.setItem(HISTORY_KEY, JSON.stringify(history.slice(0, 24))); +} + +const cursorStyle = document.createElement('style'); +cursorStyle.textContent = ` + body.eyedropper-active, + body.eyedropper-active *, + body.eyedropper-active #graph-canvas, + body.eyedropper-active .graph-canvas-container canvas, + body.eyedropper-active .litegraph canvas, + body.eyedropper-active .litegraph .node, + body.eyedropper-active .litegraph .node *, + body.eyedropper-active .drag-and-drop, + body.eyedropper-active .comfy-menu, + body.eyedropper-active .comfy-menu * { + cursor: crosshair !important; + } +`; +document.head.appendChild(cursorStyle); + +class CanvasEyedropper { + constructor() { + this.active = false; + this.lastColor = null; + this.rafId = null; + this.magnifier = this._createMagnifier(); + this._onMouseMove = this._onMouseMove.bind(this); + this._onClick = this._onClick.bind(this); + this._onKeyDown = this._onKeyDown.bind(this); + console.log("[SPLINE 🦊] CanvasEyedropper initialized"); + } + + _createMagnifier() { + const el = mkEl("div", "position:fixed;pointer-events:none;z-index:99999;width:80px;height:80px;border:2px solid #fff;border-radius:50%;box-shadow:0 0 0 2px #000,0 4px 12px rgba(0,0,0,0.5);overflow:hidden;display:none;"); + const canvas = mkEl("canvas", ""); + canvas.width = 80; + canvas.height = 80; + canvas.style.cssText = "width:80px;height:80px;image-rendering:pixelated;display:block;"; + const label = mkEl("div", "position:absolute;bottom:-24px;left:50%;transform:translateX(-50%);background:#000;color:#fff;padding:2px 6px;border-radius:3px;font-size:11px;font-family:monospace;white-space:nowrap;"); + el.append(canvas, label); + document.body.append(el); + this.magCanvas = canvas; + this.magCtx = canvas.getContext('2d', { willReadFrequently: true }); + this.magLabel = label; + return el; + } + + start(onPick, buttonRect) { + this.active = true; + this.onPick = onPick; + document.body.classList.add('eyedropper-active'); + + if (buttonRect) { + this.magnifier.style.left = (buttonRect.right + 10) + 'px'; + this.magnifier.style.top = buttonRect.top + 'px'; + } + + this.magnifier.style.display = 'block'; + document.addEventListener('mousemove', this._onMouseMove); + document.addEventListener('click', this._onClick, true); + document.addEventListener('keydown', this._onKeyDown); + console.log("[SPLINE 🦊] Eyedropper started"); + } + + stop() { + this.active = false; + document.body.classList.remove('eyedropper-active'); + this.magnifier.style.display = 'none'; + document.removeEventListener('mousemove', this._onMouseMove); + document.removeEventListener('click', this._onClick, true); + document.removeEventListener('keydown', this._onKeyDown); + if (this.rafId) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + console.log("[SPLINE ] Eyedropper stopped"); + } + + _getColorAt(x, y) { + this.magnifier.style.display = 'none'; + const el = document.elementFromPoint(x, y); + + if (!el) { + this.magnifier.style.display = 'block'; + return null; + } + + if (this.magnifier.contains(el)) { + this.magnifier.style.display = 'block'; + return null; + } + + let canvas = el.tagName === 'CANVAS' ? el : el.querySelector('canvas'); + if (!canvas) { + let parent = el.parentElement; + while (parent) { + canvas = parent.querySelector('canvas'); + if (canvas) break; + parent = parent.parentElement; + } + } + + this.magnifier.style.display = 'block'; + + if (!canvas) return null; + + const rect = canvas.getBoundingClientRect(); + const px = (x - rect.left) * (canvas.width / rect.width); + const py = (y - rect.top) * (canvas.height / rect.height); + + if (px < 0 || py < 0 || px >= canvas.width || py >= canvas.height) return null; + + try { + const ctx = canvas.getContext('2d', { willReadFrequently: true }); + const pixel = ctx.getImageData(Math.floor(px), Math.floor(py), 1, 1).data; + return { r: pixel[0], g: pixel[1], b: pixel[2], a: pixel[3] }; + } catch (e) { + console.warn("[SPLINE ] CORS error:", e.message); + return null; + } + } + + _drawMagnifier(x, y, color) { + this.magCtx.clearRect(0, 0, 80, 80); + this.magCtx.fillStyle = `rgb(${color.r},${color.g},${color.b})`; + this.magCtx.fillRect(0, 0, 80, 80); + + this.magCtx.strokeStyle = 'rgba(255,255,255,0.3)'; + this.magCtx.lineWidth = 1; + for (let i = 0; i <= 80; i += 10) { + this.magCtx.beginPath(); + this.magCtx.moveTo(i, 0); this.magCtx.lineTo(i, 80); + this.magCtx.moveTo(0, i); this.magCtx.lineTo(80, i); + this.magCtx.stroke(); + } + + this.magCtx.strokeStyle = '#fff'; + this.magCtx.lineWidth = 1; + this.magCtx.strokeRect(35.5, 35.5, 9, 9); + + const hex = rgbToHex(color.r, color.g, color.b); + this.magLabel.textContent = hex; + + this.magnifier.style.left = (x + 10) + 'px'; + this.magnifier.style.top = (y + 10) + 'px'; + } + + _onMouseMove(e) { + if (this.rafId) return; + + this.rafId = requestAnimationFrame(() => { + this.rafId = null; + const color = this._getColorAt(e.clientX, e.clientY); + if (color) { + this.lastColor = color; + this._drawMagnifier(e.clientX, e.clientY, color); + } else if (this.lastColor) { + this._drawMagnifier(e.clientX, e.clientY, this.lastColor); + } + }); + } + + _onClick(e) { + e.preventDefault(); + e.stopPropagation(); + + const color = this._getColorAt(e.clientX, e.clientY); + this.stop(); + + if (color) { + const hex = rgbToHex(color.r, color.g, color.b); + console.log("[SPLINE ] Color picked:", hex); + if (this.onPick) this.onPick(hex); + } else { + console.log("[SPLINE 🦊] No color found, cancelled"); + } + } + + _onKeyDown(e) { + if (e.key === 'Escape') { + console.log("[SPLINE 🦊] Eyedropper cancelled"); + this.stop(); + } + } +} + +const canvasEyedropper = new CanvasEyedropper(); + app.registerExtension({ name: "RSColorPicker", async beforeRegisterNodeDef(nodeType, nodeData) { if (nodeData.name !== NODE_CLASS) return; + nodeType.output = ["INT", "STRING", "STRING"]; + nodeType.output_name = ["HEX_INT", "HEX_STR", "RGB"]; + const origOnNodeCreated = nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated = function () { const result = origOnNodeCreated?.apply(this, arguments); const node = this; + + node.computeSize = () => [NODE_W, NODE_H]; + node.setSize([NODE_W, NODE_H]); + const colorWidget = node.widgets?.find(w => w.name === "color"); - + let initialColor = "#ff0000"; if (colorWidget) { + initialColor = colorWidget.value || "#ff0000"; colorWidget.hidden = true; - colorWidget.computeSize = () => [0, -4]; + colorWidget.computeSize = () => [0, 0]; } + initialColor = normalizeHex(initialColor); - const initialColor = colorWidget?.value || "#ff0000"; - const dom = buildUI(initialColor); + const root = mkEl("div", "display:flex;flex-direction:column;gap:4px;padding:3px;overflow:hidden;"); - const syncToWidget = (color) => { - if (colorWidget) colorWidget.value = color; + // Label для основных цветов + const basicLabel = mkEl("div", "color:#999;font-size:10px;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:2px;padding-left:2px;", { + textContent: "Basic Colors" + }); + + const presetsRow = mkEl("div", "display:flex;gap:3px;flex-wrap:wrap;"); + PRESETS.forEach(color => { + const btn = mkEl("div", `width:19px;height:19px;border-radius:3px;background:${color};cursor:pointer;border:1px solid #333;transition:transform 0.1s;`); + btn.onmouseenter = () => btn.style.transform = "scale(1.2)"; + btn.onmouseleave = () => btn.style.transform = "scale(1)"; + btn.onclick = () => { + swatch.style.background = color; + colorInput.value = color; + hexInput.value = color.toUpperCase(); + syncOutputs(color); + }; + presetsRow.append(btn); + }); + + const topRow = mkEl("div", "display:flex;gap:17px;align-items:center;margin-top:5px;"); + + const swatch = mkEl("div", `width:36px;height:36px;border-radius:4px;border:2px solid #444;background:${initialColor};cursor:pointer;flex-shrink:0;transition:border-color 0.1s;`); + swatch.onmouseenter = () => swatch.style.borderColor = "#6688aa"; + swatch.onmouseleave = () => swatch.style.borderColor = "#444"; + + const colorInput = mkEl("input", "width:0;height:0;opacity:0;position:absolute;", { type: "color", value: initialColor }); + + const hexInput = mkEl("input", "width:10ch;padding:4px 6px;background:#1e1e1e;color:#ccc;border:1px solid #444;border-radius:4px;font-family:monospace;font-size:12px;flex-shrink:0;box-sizing:border-box;", { value: initialColor.toUpperCase() }); + + // Иконка палитры вместо кисти + const eyedropperBtn = mkEl("button", "width:36px;height:36px;border-radius:4px;border:2px solid #444;background:#2a2a2a;color:#ccc;cursor:pointer;font-size:14px;flex-shrink:0;transition:all 0.1s;", { + innerHTML: ``, + title: "Color Palette" + }); + eyedropperBtn.onmouseenter = () => { eyedropperBtn.style.borderColor = "#6688aa"; eyedropperBtn.style.background = "#3a3a3a"; }; + eyedropperBtn.onmouseleave = () => { eyedropperBtn.style.borderColor = "#444"; eyedropperBtn.style.background = "#2a2a2a"; }; + + eyedropperBtn.onclick = async () => { + if ('EyeDropper' in window) { + try { + const eyeDropper = new EyeDropper(); + const result = await eyeDropper.open(); + const color = normalizeHex(result.sRGBHex); + swatch.style.background = color; + colorInput.value = color; + hexInput.value = color.toUpperCase(); + syncOutputs(color); + return; + } catch (e) { return; } + } + + const rect = eyedropperBtn.getBoundingClientRect(); + canvasEyedropper.start((hex) => { + const color = normalizeHex(hex); + swatch.style.background = color; + colorInput.value = color; + hexInput.value = color.toUpperCase(); + syncOutputs(color); + }, rect); + }; + + swatch.onclick = () => colorInput.click(); + swatch.ondblclick = () => { + navigator.clipboard.writeText(hexInput.value.toUpperCase()); + swatch.style.borderColor = "#50cc50"; + setTimeout(() => swatch.style.borderColor = "#444", 300); + }; + + topRow.append(swatch, colorInput, hexInput, eyedropperBtn); + + // Label для истории + const recentLabel = mkEl("div", "color:#999;font-size:10px;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:2px;padding-left:2px;margin-top:5px;", { + textContent: "Recent Colors" + }); + + const historyRow = mkEl("div", "display:flex;gap:3px;flex-wrap:wrap;"); + const updateHistory = () => { + historyRow.innerHTML = ''; + getHistory().forEach(color => { + const btn = mkEl("div", `width:19px;height:19px;border-radius:3px;background:${color};cursor:pointer;border:1px solid #333;transition:transform 0.1s;`); + btn.onmouseenter = () => btn.style.transform = "scale(1.2)"; + btn.onmouseleave = () => btn.style.transform = "scale(1)"; + btn.onclick = () => { + swatch.style.background = color; + colorInput.value = color; + hexInput.value = color.toUpperCase(); + syncOutputs(color); + }; + historyRow.append(btn); + }); + }; + updateHistory(); + + // Новый порядок: basicLabel → presetsRow → topRow → recentLabel → historyRow + root.append(basicLabel, presetsRow, topRow, recentLabel, historyRow); + + const syncOutputs = (color) => { + const normalized = normalizeHex(color); + if (colorWidget) { + colorWidget.value = normalized; + } + + const rgb = hexToRGB(normalized); + if (node.outputs) { + node.outputs[0].value = hexToInt(normalized); + node.outputs[1].value = normalized.toUpperCase(); + node.outputs[2].value = `${rgb.r.toFixed(3)}, ${rgb.g.toFixed(3)}, ${rgb.b.toFixed(3)}`; + } + + addToHistory(normalized); + updateHistory(); + if (node.graph) node.graph.setDirtyCanvas(true, true); }; - dom.colorPicker.addEventListener("input", (e) => { - const color = e.target.value; - dom.swatch.style.background = color; - dom.hexInput.value = color.toUpperCase(); - syncToWidget(color); + colorInput.addEventListener("input", (e) => { + const color = normalizeHex(e.target.value); + swatch.style.background = color; + hexInput.value = color.toUpperCase(); + syncOutputs(color); }); - dom.hexInput.addEventListener("change", (e) => { + hexInput.addEventListener("change", (e) => { let val = e.target.value.trim(); if (!val.startsWith("#")) val = "#" + val; if (/^#[0-9A-Fa-f]{6}$/.test(val)) { - dom.swatch.style.background = val; - dom.colorPicker.value = val; - dom.hexInput.value = val.toUpperCase(); - syncToWidget(val); - } else { - dom.hexInput.value = dom.colorPicker.value.toUpperCase(); + const normalized = normalizeHex(val); + swatch.style.background = normalized; + colorInput.value = normalized; + hexInput.value = normalized.toUpperCase(); + syncOutputs(normalized); } }); - const domWidget = node.addDOMWidget("rs_colorpicker", "custom", dom.root, { - serialize: false, - hideOnZoom: false, - }); + const domWidget = node.addDOMWidget("rs_colorpicker", "custom", root, { serialize: false }); - domWidget.computeSize = () => [WIDGET_W, WIDGET_H]; - node.setSize([WIDGET_W + 20, WIDGET_H + NODE_CHROME_H]); - - if (node.widgets && colorWidget) { - const idx = node.widgets.indexOf(colorWidget); - if (idx >= 0) { - node.widgets.splice(idx, 1); - node.widgets.push(colorWidget); - } - } + syncOutputs(initialColor); return result; };