@nice2dev/ui-graphics 1.3.15 → 1.3.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cjs/animation/AnimatedPerson.js +470 -143
  3. package/dist/cjs/animation/AnimationEditor.js +289 -101
  4. package/dist/cjs/animation/Audience.js +83 -61
  5. package/dist/cjs/animation/BodyRenderer.js +69 -28
  6. package/dist/cjs/animation/animationHelper.js +281 -288
  7. package/dist/cjs/animation/characterTypes.js +200 -203
  8. package/dist/cjs/animation/choreoDSL.js +205 -219
  9. package/dist/cjs/animation/choreography.js +4 -32
  10. package/dist/cjs/animation/karaokeIntegration.js +68 -77
  11. package/dist/cjs/animation/lottieExport.js +108 -114
  12. package/dist/cjs/animation/motionPath.js +225 -248
  13. package/dist/cjs/animation/physics.js +227 -254
  14. package/dist/cjs/animation/rig/RigPlayer.js +53 -56
  15. package/dist/cjs/animation/rig/RiggedBody.js +71 -26
  16. package/dist/cjs/animation/rig/choreoRigBridge.js +72 -77
  17. package/dist/cjs/animation/rig/ik.js +13 -18
  18. package/dist/cjs/animation/rig/poseOps.js +34 -41
  19. package/dist/cjs/animation/rig/presets.js +64 -72
  20. package/dist/cjs/animation/rig/rigMath.js +25 -32
  21. package/dist/cjs/animation/rig/rigTypes.js +59 -63
  22. package/dist/cjs/animation/shapes/arms.js +31 -13
  23. package/dist/cjs/animation/shapes/eyes.js +129 -47
  24. package/dist/cjs/animation/shapes/hair.js +72 -45
  25. package/dist/cjs/animation/shapes/heads.js +131 -57
  26. package/dist/cjs/animation/shapes/headwear.js +47 -38
  27. package/dist/cjs/animation/shapes/legs.js +31 -13
  28. package/dist/cjs/animation/shapes/mouths.js +120 -44
  29. package/dist/cjs/animation/shapes/noses.js +80 -25
  30. package/dist/cjs/animation/shapes/outfits.js +101 -39
  31. package/dist/cjs/animation/shapes/torsos.js +15 -11
  32. package/dist/cjs/animation/spineExport.js +103 -121
  33. package/dist/cjs/core/LocalUI.js +108 -43
  34. package/dist/cjs/core/NiceBrushEngine.js +758 -552
  35. package/dist/cjs/core/collaboration.js +249 -251
  36. package/dist/cjs/core/colorBlindness.js +100 -115
  37. package/dist/cjs/core/dragDrop.js +155 -158
  38. package/dist/cjs/core/editorAsk.js +51 -41
  39. package/dist/cjs/core/fixes.js +140 -171
  40. package/dist/cjs/core/gridGuides.js +166 -201
  41. package/dist/cjs/core/historyVisual.js +156 -135
  42. package/dist/cjs/core/i18n.js +13 -14
  43. package/dist/cjs/core/integrations.js +238 -249
  44. package/dist/cjs/core/minimap.js +149 -151
  45. package/dist/cjs/core/plugins.js +98 -113
  46. package/dist/cjs/core/rtl.js +71 -83
  47. package/dist/cjs/core/shortcuts.js +196 -225
  48. package/dist/cjs/core/theme.js +142 -149
  49. package/dist/cjs/core/yjsCollaboration.js +232 -293
  50. package/dist/cjs/design/design-tools.js +12 -22
  51. package/dist/cjs/font/NiceFontEditor.js +877 -316
  52. package/dist/cjs/font/fontFormatUtils.js +388 -448
  53. package/dist/cjs/game/GameAsset2dEditor.js +1201 -392
  54. package/dist/cjs/game/GameAsset2dEditor.module.css.js +146 -5
  55. package/dist/cjs/game/gameAssetTypes.js +7 -16
  56. package/dist/cjs/game/gameAssetUtils.js +390 -413
  57. package/dist/cjs/game/useGameAssetEditor.js +820 -763
  58. package/dist/cjs/index.js +81 -84
  59. package/dist/cjs/nice2dev-ui-graphics.css +1 -1
  60. package/dist/cjs/node_modules/lib0/array.js +8 -70
  61. package/dist/cjs/node_modules/lib0/binary.js +3 -20
  62. package/dist/cjs/node_modules/lib0/broadcastchannel.js +24 -86
  63. package/dist/cjs/node_modules/lib0/buffer.js +15 -82
  64. package/dist/cjs/node_modules/lib0/conditions.js +3 -16
  65. package/dist/cjs/node_modules/lib0/decoding.js +108 -287
  66. package/dist/cjs/node_modules/lib0/dom.js +14 -44
  67. package/dist/cjs/node_modules/lib0/encoding.js +68 -418
  68. package/dist/cjs/node_modules/lib0/environment.js +26 -92
  69. package/dist/cjs/node_modules/lib0/error.js +5 -29
  70. package/dist/cjs/node_modules/lib0/function.js +27 -66
  71. package/dist/cjs/node_modules/lib0/iterator.js +8 -33
  72. package/dist/cjs/node_modules/lib0/logging.common.js +12 -22
  73. package/dist/cjs/node_modules/lib0/logging.js +32 -71
  74. package/dist/cjs/node_modules/lib0/map.js +16 -86
  75. package/dist/cjs/node_modules/lib0/math.js +3 -32
  76. package/dist/cjs/node_modules/lib0/number.js +4 -15
  77. package/dist/cjs/node_modules/lib0/object.js +14 -88
  78. package/dist/cjs/node_modules/lib0/observable.js +38 -78
  79. package/dist/cjs/node_modules/lib0/pair.js +3 -20
  80. package/dist/cjs/node_modules/lib0/prng.js +8 -77
  81. package/dist/cjs/node_modules/lib0/promise.js +6 -30
  82. package/dist/cjs/node_modules/lib0/random.js +8 -23
  83. package/dist/cjs/node_modules/lib0/schema.js +313 -687
  84. package/dist/cjs/node_modules/lib0/set.js +3 -10
  85. package/dist/cjs/node_modules/lib0/storage.js +20 -55
  86. package/dist/cjs/node_modules/lib0/string.js +17 -72
  87. package/dist/cjs/node_modules/lib0/symbol.js +2 -12
  88. package/dist/cjs/node_modules/lib0/time.js +2 -15
  89. package/dist/cjs/node_modules/lib0/trait/equality.js +3 -28
  90. package/dist/cjs/node_modules/lib0/url.js +4 -18
  91. package/dist/cjs/node_modules/lib0/webcrypto.js +2 -5
  92. package/dist/cjs/node_modules/y-protocols/auth.js +5 -19
  93. package/dist/cjs/node_modules/y-protocols/awareness.js +53 -116
  94. package/dist/cjs/node_modules/y-protocols/sync.js +16 -103
  95. package/dist/cjs/node_modules/y-websocket/src/y-websocket.js +77 -219
  96. package/dist/cjs/node_modules/yjs/dist/yjs.js +2128 -4868
  97. package/dist/cjs/photo/FilterThumb.js +37 -25
  98. package/dist/cjs/photo/NiceAITools.js +679 -467
  99. package/dist/cjs/photo/PhotoEditor.js +34 -20
  100. package/dist/cjs/photo/PhotoEditor.module.css.js +296 -5
  101. package/dist/cjs/photo/PhotoEditorCanvas.js +250 -91
  102. package/dist/cjs/photo/PhotoEditorLeftPanel.js +1086 -224
  103. package/dist/cjs/photo/PhotoEditorToolbar.js +135 -19
  104. package/dist/cjs/photo/photoEditorActions.js +89 -72
  105. package/dist/cjs/photo/photoEditorTypes.js +29 -32
  106. package/dist/cjs/photo/photoFilters.js +959 -985
  107. package/dist/cjs/photo/photoOverlays.js +899 -945
  108. package/dist/cjs/photo/photoSelection.js +427 -499
  109. package/dist/cjs/photo/usePhotoEditor.js +739 -741
  110. package/dist/cjs/pixel/HSVPicker.js +191 -175
  111. package/dist/cjs/pixel/PixelEditor.js +159 -48
  112. package/dist/cjs/pixel/PixelEditor.module.css.js +209 -5
  113. package/dist/cjs/pixel/PixelEditorCanvas.js +29 -16
  114. package/dist/cjs/pixel/PixelEditorMenuBar.js +160 -162
  115. package/dist/cjs/pixel/PixelEditorRightPanel.js +377 -45
  116. package/dist/cjs/pixel/PixelEditorStatusBar.js +47 -20
  117. package/dist/cjs/pixel/PixelEditorTimeline.js +106 -23
  118. package/dist/cjs/pixel/PixelEditorToolbar.js +99 -21
  119. package/dist/cjs/pixel/SpriteGeneratorPanel.js +483 -303
  120. package/dist/cjs/pixel/animalGeneratorPresets.js +776 -601
  121. package/dist/cjs/pixel/asepriteFormat.js +511 -560
  122. package/dist/cjs/pixel/buildingGeneratorPresets.js +693 -520
  123. package/dist/cjs/pixel/characterGeneratorPresets.js +644 -544
  124. package/dist/cjs/pixel/pixel-pro.js +52 -63
  125. package/dist/cjs/pixel/pixelEditorExports.js +388 -428
  126. package/dist/cjs/pixel/pixelEditorTypes.js +91 -108
  127. package/dist/cjs/pixel/pixelEditorUtils.js +519 -561
  128. package/dist/cjs/pixel/spriteGeneratorCore.js +206 -257
  129. package/dist/cjs/pixel/usePixelEditor.js +1321 -1343
  130. package/dist/cjs/texture/Nice3DTexturePainter.js +707 -440
  131. package/dist/cjs/ui/NiceUIDesigner.js +954 -527
  132. package/dist/cjs/vector/NiceSvgToCode.js +586 -446
  133. package/dist/cjs/vector/VectorEditor.js +290 -117
  134. package/dist/cjs/vector/VectorEditor.module.css.js +86 -5
  135. package/dist/cjs/vector/VectorEditorMenuBar.js +333 -275
  136. package/dist/cjs/vector/VectorEditorRightPanel.js +1094 -371
  137. package/dist/cjs/vector/VectorEditorShapeRenderer.js +198 -60
  138. package/dist/cjs/vector/VectorEditorStatusBar.js +39 -13
  139. package/dist/cjs/vector/useVectorEditor.js +848 -889
  140. package/dist/cjs/vector/vectorAnimation.js +110 -137
  141. package/dist/cjs/vector/vectorBooleanOps.js +398 -485
  142. package/dist/cjs/vector/vectorEditorExport.js +201 -203
  143. package/dist/cjs/vector/vectorEditorImport.js +173 -189
  144. package/dist/cjs/vector/vectorEditorTypes.js +42 -49
  145. package/dist/cjs/vector/vectorGradients.js +151 -174
  146. package/dist/cjs/vector/vectorIconExport.js +52 -59
  147. package/dist/cjs/vector/vectorIconImport.js +169 -219
  148. package/dist/esm/animation/AnimatedPerson.js +470 -141
  149. package/dist/esm/animation/AnimationEditor.js +290 -100
  150. package/dist/esm/animation/Audience.js +83 -59
  151. package/dist/esm/animation/BodyRenderer.js +71 -25
  152. package/dist/esm/animation/animationHelper.js +284 -287
  153. package/dist/esm/animation/characterTypes.js +204 -202
  154. package/dist/esm/animation/choreoDSL.js +212 -218
  155. package/dist/esm/animation/choreography.js +8 -31
  156. package/dist/esm/animation/karaokeIntegration.js +74 -76
  157. package/dist/esm/animation/lottieExport.js +112 -113
  158. package/dist/esm/animation/motionPath.js +235 -247
  159. package/dist/esm/animation/physics.js +235 -253
  160. package/dist/esm/animation/rig/RigPlayer.js +53 -54
  161. package/dist/esm/animation/rig/RiggedBody.js +71 -24
  162. package/dist/esm/animation/rig/choreoRigBridge.js +74 -76
  163. package/dist/esm/animation/rig/ik.js +14 -17
  164. package/dist/esm/animation/rig/poseOps.js +37 -40
  165. package/dist/esm/animation/rig/presets.js +70 -71
  166. package/dist/esm/animation/rig/rigMath.js +28 -31
  167. package/dist/esm/animation/rig/rigTypes.js +62 -62
  168. package/dist/esm/animation/shapes/arms.js +32 -12
  169. package/dist/esm/animation/shapes/eyes.js +135 -46
  170. package/dist/esm/animation/shapes/hair.js +80 -44
  171. package/dist/esm/animation/shapes/heads.js +140 -56
  172. package/dist/esm/animation/shapes/headwear.js +54 -37
  173. package/dist/esm/animation/shapes/legs.js +32 -12
  174. package/dist/esm/animation/shapes/mouths.js +127 -43
  175. package/dist/esm/animation/shapes/noses.js +87 -24
  176. package/dist/esm/animation/shapes/outfits.js +106 -38
  177. package/dist/esm/animation/shapes/torsos.js +16 -10
  178. package/dist/esm/animation/spineExport.js +106 -120
  179. package/dist/esm/core/LocalUI.js +111 -42
  180. package/dist/esm/core/NiceBrushEngine.js +761 -551
  181. package/dist/esm/core/collaboration.js +255 -250
  182. package/dist/esm/core/colorBlindness.js +106 -114
  183. package/dist/esm/core/dragDrop.js +157 -157
  184. package/dist/esm/core/editorAsk.js +53 -40
  185. package/dist/esm/core/fixes.js +145 -170
  186. package/dist/esm/core/gridGuides.js +178 -200
  187. package/dist/esm/core/historyVisual.js +158 -134
  188. package/dist/esm/core/i18n.js +15 -13
  189. package/dist/esm/core/integrations.js +244 -248
  190. package/dist/esm/core/minimap.js +151 -150
  191. package/dist/esm/core/plugins.js +102 -112
  192. package/dist/esm/core/rtl.js +77 -82
  193. package/dist/esm/core/shortcuts.js +200 -224
  194. package/dist/esm/core/theme.js +150 -148
  195. package/dist/esm/core/yjsCollaboration.js +240 -292
  196. package/dist/esm/design/design-tools.js +13 -21
  197. package/dist/esm/font/NiceFontEditor.js +880 -315
  198. package/dist/esm/font/fontFormatUtils.js +394 -447
  199. package/dist/esm/game/GameAsset2dEditor.js +1202 -391
  200. package/dist/esm/game/GameAsset2dEditor.module.css.js +146 -3
  201. package/dist/esm/game/gameAssetTypes.js +8 -15
  202. package/dist/esm/game/gameAssetUtils.js +408 -412
  203. package/dist/esm/game/useGameAssetEditor.js +821 -762
  204. package/dist/esm/index.js +332 -69
  205. package/dist/esm/nice2dev-ui-graphics.css +1 -1
  206. package/dist/esm/node_modules/lib0/array.js +15 -69
  207. package/dist/esm/node_modules/lib0/binary.js +14 -19
  208. package/dist/esm/node_modules/lib0/broadcastchannel.js +27 -85
  209. package/dist/esm/node_modules/lib0/buffer.js +20 -80
  210. package/dist/esm/node_modules/lib0/conditions.js +4 -15
  211. package/dist/esm/node_modules/lib0/decoding.js +128 -286
  212. package/dist/esm/node_modules/lib0/dom.js +20 -43
  213. package/dist/esm/node_modules/lib0/encoding.js +90 -415
  214. package/dist/esm/node_modules/lib0/environment.js +33 -91
  215. package/dist/esm/node_modules/lib0/error.js +8 -28
  216. package/dist/esm/node_modules/lib0/function.js +31 -65
  217. package/dist/esm/node_modules/lib0/iterator.js +11 -32
  218. package/dist/esm/node_modules/lib0/logging.common.js +22 -21
  219. package/dist/esm/node_modules/lib0/logging.js +44 -70
  220. package/dist/esm/node_modules/lib0/map.js +21 -85
  221. package/dist/esm/node_modules/lib0/math.js +9 -31
  222. package/dist/esm/node_modules/lib0/number.js +7 -14
  223. package/dist/esm/node_modules/lib0/object.js +26 -87
  224. package/dist/esm/node_modules/lib0/observable.js +40 -77
  225. package/dist/esm/node_modules/lib0/pair.js +5 -19
  226. package/dist/esm/node_modules/lib0/prng.js +15 -76
  227. package/dist/esm/node_modules/lib0/promise.js +7 -29
  228. package/dist/esm/node_modules/lib0/random.js +10 -22
  229. package/dist/esm/node_modules/lib0/schema.js +367 -686
  230. package/dist/esm/node_modules/lib0/set.js +4 -9
  231. package/dist/esm/node_modules/lib0/storage.js +23 -54
  232. package/dist/esm/node_modules/lib0/string.js +26 -71
  233. package/dist/esm/node_modules/lib0/symbol.js +3 -11
  234. package/dist/esm/node_modules/lib0/time.js +3 -14
  235. package/dist/esm/node_modules/lib0/trait/equality.js +5 -27
  236. package/dist/esm/node_modules/lib0/url.js +5 -17
  237. package/dist/esm/node_modules/lib0/webcrypto.js +3 -4
  238. package/dist/esm/node_modules/y-protocols/auth.js +7 -18
  239. package/dist/esm/node_modules/y-protocols/awareness.js +58 -115
  240. package/dist/esm/node_modules/y-protocols/sync.js +26 -102
  241. package/dist/esm/node_modules/y-websocket/src/y-websocket.js +82 -218
  242. package/dist/esm/node_modules/yjs/dist/yjs.js +2263 -4899
  243. package/dist/esm/photo/FilterThumb.js +37 -23
  244. package/dist/esm/photo/NiceAITools.js +685 -466
  245. package/dist/esm/photo/PhotoEditor.js +34 -18
  246. package/dist/esm/photo/PhotoEditor.module.css.js +296 -3
  247. package/dist/esm/photo/PhotoEditorCanvas.js +250 -89
  248. package/dist/esm/photo/PhotoEditorLeftPanel.js +1086 -222
  249. package/dist/esm/photo/PhotoEditorToolbar.js +135 -17
  250. package/dist/esm/photo/photoEditorActions.js +93 -71
  251. package/dist/esm/photo/photoEditorTypes.js +32 -31
  252. package/dist/esm/photo/photoFilters.js +967 -984
  253. package/dist/esm/photo/photoOverlays.js +913 -944
  254. package/dist/esm/photo/photoSelection.js +438 -498
  255. package/dist/esm/photo/usePhotoEditor.js +740 -740
  256. package/dist/esm/pixel/HSVPicker.js +192 -174
  257. package/dist/esm/pixel/PixelEditor.js +160 -47
  258. package/dist/esm/pixel/PixelEditor.module.css.js +209 -3
  259. package/dist/esm/pixel/PixelEditorCanvas.js +29 -14
  260. package/dist/esm/pixel/PixelEditorMenuBar.js +160 -160
  261. package/dist/esm/pixel/PixelEditorRightPanel.js +377 -43
  262. package/dist/esm/pixel/PixelEditorStatusBar.js +47 -18
  263. package/dist/esm/pixel/PixelEditorTimeline.js +106 -21
  264. package/dist/esm/pixel/PixelEditorToolbar.js +99 -19
  265. package/dist/esm/pixel/SpriteGeneratorPanel.js +483 -301
  266. package/dist/esm/pixel/animalGeneratorPresets.js +782 -600
  267. package/dist/esm/pixel/asepriteFormat.js +517 -559
  268. package/dist/esm/pixel/buildingGeneratorPresets.js +699 -519
  269. package/dist/esm/pixel/characterGeneratorPresets.js +650 -543
  270. package/dist/esm/pixel/pixel-pro.js +53 -62
  271. package/dist/esm/pixel/pixelEditorExports.js +397 -427
  272. package/dist/esm/pixel/pixelEditorTypes.js +103 -107
  273. package/dist/esm/pixel/pixelEditorUtils.js +551 -560
  274. package/dist/esm/pixel/spriteGeneratorCore.js +223 -256
  275. package/dist/esm/pixel/usePixelEditor.js +1322 -1342
  276. package/dist/esm/texture/Nice3DTexturePainter.js +709 -439
  277. package/dist/esm/ui/NiceUIDesigner.js +957 -526
  278. package/dist/esm/vector/NiceSvgToCode.js +589 -445
  279. package/dist/esm/vector/VectorEditor.js +291 -116
  280. package/dist/esm/vector/VectorEditor.module.css.js +86 -3
  281. package/dist/esm/vector/VectorEditorMenuBar.js +333 -273
  282. package/dist/esm/vector/VectorEditorRightPanel.js +1094 -369
  283. package/dist/esm/vector/VectorEditorShapeRenderer.js +199 -59
  284. package/dist/esm/vector/VectorEditorStatusBar.js +39 -11
  285. package/dist/esm/vector/useVectorEditor.js +849 -888
  286. package/dist/esm/vector/vectorAnimation.js +115 -136
  287. package/dist/esm/vector/vectorBooleanOps.js +401 -484
  288. package/dist/esm/vector/vectorEditorExport.js +207 -202
  289. package/dist/esm/vector/vectorEditorImport.js +174 -188
  290. package/dist/esm/vector/vectorEditorTypes.js +50 -48
  291. package/dist/esm/vector/vectorGradients.js +158 -173
  292. package/dist/esm/vector/vectorIconExport.js +53 -58
  293. package/dist/esm/vector/vectorIconImport.js +171 -218
  294. package/package.json +9 -15
@@ -1,451 +1,718 @@
1
- 'use strict';
2
-
3
- var jsxRuntime = require('react/jsx-runtime');
4
- var uiCore = require('@nice2dev/ui-core');
5
- var React = require('react');
6
- var i18n = require('../core/i18n.js');
7
-
8
- // ── Default Config ──────────────────────────────────────────────
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const uiCore = require("@nice2dev/ui-core");
5
+ const React = require("react");
6
+ const i18n = require("../core/i18n.js");
9
7
  const DEFAULT_BRUSH = {
10
- size: 20,
11
- opacity: 1,
12
- hardness: 0.8,
13
- // Paint, and the value of a colour input (which needs #rrggbb): a var() here made the
14
- // browser warn and show black.
15
- color: '#ffffff',
16
- blendMode: 'normal',
8
+ size: 20,
9
+ opacity: 1,
10
+ hardness: 0.8,
11
+ // Paint, and the value of a colour input (which needs #rrggbb): a var() here made the
12
+ // browser warn and show black.
13
+ color: "#ffffff",
14
+ blendMode: "normal"
17
15
  };
18
- // ── Hook ────────────────────────────────────────────────────────
19
16
  function useTexturePainter(width, height) {
20
- const [brush, setBrush] = React.useState(DEFAULT_BRUSH);
21
- const [layers, setLayers] = React.useState([]);
22
- const [activeLayerId, setActiveLayerId] = React.useState(null);
23
- const [tool, setTool] = React.useState('brush');
24
- const [renderVersion, setRenderVersion] = React.useState(0);
25
- const undoStackRef = React.useRef([]);
26
- React.useEffect(() => {
27
- if (typeof document === 'undefined') {
28
- return;
29
- }
30
- const canvas = document.createElement('canvas');
31
- canvas.width = width;
32
- canvas.height = height;
33
- const ctx = canvas.getContext('2d');
34
- if (ctx) {
35
- ctx.fillStyle = uiCore.resolveCssColor('var(--nice-text-secondary, #808080)', ctx.canvas);
36
- ctx.fillRect(0, 0, width, height);
37
- }
38
- const layer = {
39
- id: 'base',
40
- name: 'Base Color',
41
- visible: true,
42
- opacity: 1,
43
- canvas,
44
- };
45
- setLayers([layer]);
46
- setActiveLayerId('base');
47
- }, [width, height]);
48
- const addLayer = React.useCallback((name) => {
49
- if (typeof document === 'undefined') {
50
- return;
51
- }
52
- const canvas = document.createElement('canvas');
53
- canvas.width = width;
54
- canvas.height = height;
55
- const id = `layer-${Date.now()}`;
56
- setLayers((prev) => [...prev, { id, name, visible: true, opacity: 1, canvas }]);
57
- setActiveLayerId(id);
58
- }, [width, height]);
59
- const removeLayer = React.useCallback((id) => {
60
- setLayers((prev) => prev.filter((l) => l.id !== id));
61
- setActiveLayerId((prev) => (prev === id ? null : prev));
62
- }, []);
63
- const toggleLayerVisibility = React.useCallback((id) => {
64
- setLayers((prev) => prev.map((l) => (l.id === id ? { ...l, visible: !l.visible } : l)));
65
- setRenderVersion((v) => v + 1);
66
- }, []);
67
- const setLayerOpacity = React.useCallback((id, opacity) => {
68
- setLayers((prev) => prev.map((l) => (l.id === id ? { ...l, opacity } : l)));
69
- setRenderVersion((v) => v + 1);
70
- }, []);
71
- const beginStroke = React.useCallback(() => {
72
- const layer = layers.find((l) => l.id === activeLayerId);
73
- if (!layer) {
74
- return;
75
- }
76
- const ctx = layer.canvas.getContext('2d');
77
- if (!ctx) {
78
- return;
79
- }
80
- undoStackRef.current.push({
81
- layerId: layer.id,
82
- imageData: ctx.getImageData(0, 0, layer.canvas.width, layer.canvas.height),
83
- });
84
- if (undoStackRef.current.length > 40) {
85
- undoStackRef.current.shift();
86
- }
87
- }, [layers, activeLayerId]);
88
- const undo = React.useCallback(() => {
89
- const snapshot = undoStackRef.current.pop();
90
- if (!snapshot) {
91
- return;
92
- }
93
- const layer = layers.find((l) => l.id === snapshot.layerId);
94
- if (!layer) {
95
- return;
96
- }
97
- const ctx = layer.canvas.getContext('2d');
98
- if (!ctx) {
99
- return;
100
- }
101
- ctx.putImageData(snapshot.imageData, 0, 0);
102
- setRenderVersion((v) => v + 1);
103
- }, [layers]);
104
- const eyedrop = React.useCallback((x, y) => {
105
- // Sample composite color from all visible layers
106
- if (typeof document === 'undefined') {
107
- return;
108
- }
109
- const tmp = document.createElement('canvas');
110
- tmp.width = width;
111
- tmp.height = height;
112
- const ctx = tmp.getContext('2d');
113
- if (!ctx) {
114
- return;
115
- }
116
- for (const layer of layers) {
117
- if (!layer.visible) {
118
- continue;
119
- }
120
- ctx.globalAlpha = layer.opacity;
121
- ctx.drawImage(layer.canvas, 0, 0);
122
- }
123
- const px = ctx.getImageData(Math.round(x), Math.round(y), 1, 1).data;
124
- const hex = `#${px[0].toString(16).padStart(2, '0')}${px[1].toString(16).padStart(2, '0')}${px[2].toString(16).padStart(2, '0')}`;
125
- setBrush((b) => ({ ...b, color: hex }));
126
- setTool('brush');
127
- }, [layers, width, height]);
128
- const paintStroke = React.useCallback((x, y) => {
129
- const layer = layers.find((l) => l.id === activeLayerId);
130
- if (!layer) {
131
- return;
132
- }
133
- const ctx = layer.canvas.getContext('2d');
134
- if (!ctx) {
135
- return;
136
- }
137
- ctx.globalAlpha = brush.opacity;
138
- if (tool === 'eraser') {
139
- ctx.globalCompositeOperation = 'destination-out';
140
- ctx.beginPath();
141
- ctx.arc(x, y, brush.size / 2, 0, Math.PI * 2);
142
- ctx.fill();
143
- ctx.globalCompositeOperation = 'source-over';
144
- }
145
- else if (tool === 'brush') {
146
- const gradient = ctx.createRadialGradient(x, y, 0, x, y, brush.size / 2);
147
- gradient.addColorStop(0, brush.color);
148
- gradient.addColorStop(brush.hardness, brush.color);
149
- gradient.addColorStop(1, 'transparent');
150
- ctx.fillStyle = gradient;
151
- ctx.beginPath();
152
- ctx.arc(x, y, brush.size / 2, 0, Math.PI * 2);
153
- ctx.fill();
154
- }
155
- else if (tool === 'fill') {
156
- ctx.fillStyle = brush.color;
157
- ctx.fillRect(0, 0, width, height);
158
- }
159
- ctx.globalAlpha = 1;
160
- setRenderVersion((v) => v + 1);
161
- }, [layers, activeLayerId, brush, tool, width, height]);
162
- const flattenToBlob = React.useCallback(async () => {
163
- if (typeof document === 'undefined') {
164
- return null;
165
- }
166
- const canvas = document.createElement('canvas');
167
- canvas.width = width;
168
- canvas.height = height;
169
- const ctx = canvas.getContext('2d');
170
- if (!ctx) {
171
- return null;
172
- }
173
- for (const layer of layers) {
174
- if (!layer.visible) {
175
- continue;
176
- }
177
- ctx.globalAlpha = layer.opacity;
178
- ctx.drawImage(layer.canvas, 0, 0);
17
+ const [brush, setBrush] = React.useState(DEFAULT_BRUSH);
18
+ const [layers, setLayers] = React.useState([]);
19
+ const [activeLayerId, setActiveLayerId] = React.useState(null);
20
+ const [tool, setTool] = React.useState("brush");
21
+ const [renderVersion, setRenderVersion] = React.useState(0);
22
+ const undoStackRef = React.useRef([]);
23
+ React.useEffect(() => {
24
+ if (typeof document === "undefined") {
25
+ return;
26
+ }
27
+ const canvas = document.createElement("canvas");
28
+ canvas.width = width;
29
+ canvas.height = height;
30
+ const ctx = canvas.getContext("2d");
31
+ if (ctx) {
32
+ ctx.fillStyle = uiCore.resolveCssColor("var(--nice-text-secondary, #808080)", ctx.canvas);
33
+ ctx.fillRect(0, 0, width, height);
34
+ }
35
+ const layer = {
36
+ id: "base",
37
+ name: "Base Color",
38
+ visible: true,
39
+ opacity: 1,
40
+ canvas
41
+ };
42
+ setLayers([layer]);
43
+ setActiveLayerId("base");
44
+ }, [width, height]);
45
+ const addLayer = React.useCallback(
46
+ (name) => {
47
+ if (typeof document === "undefined") {
48
+ return;
49
+ }
50
+ const canvas = document.createElement("canvas");
51
+ canvas.width = width;
52
+ canvas.height = height;
53
+ const id = `layer-${Date.now()}`;
54
+ setLayers((prev) => [...prev, { id, name, visible: true, opacity: 1, canvas }]);
55
+ setActiveLayerId(id);
56
+ },
57
+ [width, height]
58
+ );
59
+ const removeLayer = React.useCallback((id) => {
60
+ setLayers((prev) => prev.filter((l) => l.id !== id));
61
+ setActiveLayerId((prev) => prev === id ? null : prev);
62
+ }, []);
63
+ const toggleLayerVisibility = React.useCallback((id) => {
64
+ setLayers((prev) => prev.map((l) => l.id === id ? { ...l, visible: !l.visible } : l));
65
+ setRenderVersion((v) => v + 1);
66
+ }, []);
67
+ const setLayerOpacity = React.useCallback((id, opacity) => {
68
+ setLayers((prev) => prev.map((l) => l.id === id ? { ...l, opacity } : l));
69
+ setRenderVersion((v) => v + 1);
70
+ }, []);
71
+ const beginStroke = React.useCallback(() => {
72
+ const layer = layers.find((l) => l.id === activeLayerId);
73
+ if (!layer) {
74
+ return;
75
+ }
76
+ const ctx = layer.canvas.getContext("2d");
77
+ if (!ctx) {
78
+ return;
79
+ }
80
+ undoStackRef.current.push({
81
+ layerId: layer.id,
82
+ imageData: ctx.getImageData(0, 0, layer.canvas.width, layer.canvas.height)
83
+ });
84
+ if (undoStackRef.current.length > 40) {
85
+ undoStackRef.current.shift();
86
+ }
87
+ }, [layers, activeLayerId]);
88
+ const undo = React.useCallback(() => {
89
+ const snapshot = undoStackRef.current.pop();
90
+ if (!snapshot) {
91
+ return;
92
+ }
93
+ const layer = layers.find((l) => l.id === snapshot.layerId);
94
+ if (!layer) {
95
+ return;
96
+ }
97
+ const ctx = layer.canvas.getContext("2d");
98
+ if (!ctx) {
99
+ return;
100
+ }
101
+ ctx.putImageData(snapshot.imageData, 0, 0);
102
+ setRenderVersion((v) => v + 1);
103
+ }, [layers]);
104
+ const eyedrop = React.useCallback(
105
+ (x, y) => {
106
+ if (typeof document === "undefined") {
107
+ return;
108
+ }
109
+ const tmp = document.createElement("canvas");
110
+ tmp.width = width;
111
+ tmp.height = height;
112
+ const ctx = tmp.getContext("2d");
113
+ if (!ctx) {
114
+ return;
115
+ }
116
+ for (const layer of layers) {
117
+ if (!layer.visible) {
118
+ continue;
179
119
  }
180
- ctx.globalAlpha = 1;
181
- return new Promise((resolve) => canvas.toBlob(resolve, 'image/png'));
182
- }, [layers, width, height]);
120
+ ctx.globalAlpha = layer.opacity;
121
+ ctx.drawImage(layer.canvas, 0, 0);
122
+ }
123
+ const px = ctx.getImageData(Math.round(x), Math.round(y), 1, 1).data;
124
+ const hex = `#${px[0].toString(16).padStart(2, "0")}${px[1].toString(16).padStart(2, "0")}${px[2].toString(16).padStart(2, "0")}`;
125
+ setBrush((b) => ({ ...b, color: hex }));
126
+ setTool("brush");
127
+ },
128
+ [layers, width, height]
129
+ );
130
+ const paintStroke = React.useCallback(
131
+ (x, y) => {
132
+ const layer = layers.find((l) => l.id === activeLayerId);
133
+ if (!layer) {
134
+ return;
135
+ }
136
+ const ctx = layer.canvas.getContext("2d");
137
+ if (!ctx) {
138
+ return;
139
+ }
140
+ ctx.globalAlpha = brush.opacity;
141
+ if (tool === "eraser") {
142
+ ctx.globalCompositeOperation = "destination-out";
143
+ ctx.beginPath();
144
+ ctx.arc(x, y, brush.size / 2, 0, Math.PI * 2);
145
+ ctx.fill();
146
+ ctx.globalCompositeOperation = "source-over";
147
+ } else if (tool === "brush") {
148
+ const gradient = ctx.createRadialGradient(x, y, 0, x, y, brush.size / 2);
149
+ gradient.addColorStop(0, brush.color);
150
+ gradient.addColorStop(brush.hardness, brush.color);
151
+ gradient.addColorStop(1, "transparent");
152
+ ctx.fillStyle = gradient;
153
+ ctx.beginPath();
154
+ ctx.arc(x, y, brush.size / 2, 0, Math.PI * 2);
155
+ ctx.fill();
156
+ } else if (tool === "fill") {
157
+ ctx.fillStyle = brush.color;
158
+ ctx.fillRect(0, 0, width, height);
159
+ }
160
+ ctx.globalAlpha = 1;
161
+ setRenderVersion((v) => v + 1);
162
+ },
163
+ [layers, activeLayerId, brush, tool, width, height]
164
+ );
165
+ const flattenToBlob = React.useCallback(async () => {
166
+ if (typeof document === "undefined") {
167
+ return null;
168
+ }
169
+ const canvas = document.createElement("canvas");
170
+ canvas.width = width;
171
+ canvas.height = height;
172
+ const ctx = canvas.getContext("2d");
173
+ if (!ctx) {
174
+ return null;
175
+ }
176
+ for (const layer of layers) {
177
+ if (!layer.visible) {
178
+ continue;
179
+ }
180
+ ctx.globalAlpha = layer.opacity;
181
+ ctx.drawImage(layer.canvas, 0, 0);
182
+ }
183
+ ctx.globalAlpha = 1;
184
+ return new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
185
+ }, [layers, width, height]);
186
+ return {
187
+ brush,
188
+ setBrush,
189
+ layers,
190
+ addLayer,
191
+ removeLayer,
192
+ activeLayerId,
193
+ setActiveLayerId,
194
+ tool,
195
+ setTool,
196
+ paintStroke,
197
+ flattenToBlob,
198
+ beginStroke,
199
+ undo,
200
+ canUndo: undoStackRef.current.length > 0,
201
+ toggleLayerVisibility,
202
+ setLayerOpacity,
203
+ eyedrop,
204
+ renderVersion
205
+ };
206
+ }
207
+ const Nice3DTexturePainter = ({
208
+ textureWidth = 1024,
209
+ textureHeight = 1024,
210
+ onSave,
211
+ className = ""
212
+ }) => {
213
+ const { t } = i18n.useNiceTranslation();
214
+ const painter = useTexturePainter(textureWidth, textureHeight);
215
+ const canvasRef = React.useRef(null);
216
+ const isPainting = React.useRef(false);
217
+ const [zoom, setZoom] = React.useState(0.5);
218
+ const [panOffset, setPanOffset] = React.useState({ x: 0, y: 0 });
219
+ const isPanning = React.useRef(false);
220
+ const lastPan = React.useRef({ x: 0, y: 0 });
221
+ const [cursorPos, setCursorPos] = React.useState(null);
222
+ const pw = Math.round(textureWidth * zoom);
223
+ const ph = Math.round(textureHeight * zoom);
224
+ React.useEffect(() => {
225
+ const canvas = canvasRef.current;
226
+ if (!canvas) {
227
+ return;
228
+ }
229
+ const ctx = canvas.getContext("2d");
230
+ if (!ctx) {
231
+ return;
232
+ }
233
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
234
+ const size = 8;
235
+ const checkA = uiCore.resolveCssColor("var(--nice-border, #ccc)", canvas);
236
+ const checkB = uiCore.resolveCssColor("var(--nice-bg, #fff)", canvas);
237
+ for (let y = 0; y < canvas.height; y += size) {
238
+ for (let x = 0; x < canvas.width; x += size) {
239
+ ctx.fillStyle = (x + y) / size % 2 === 0 ? checkA : checkB;
240
+ ctx.fillRect(x, y, size, size);
241
+ }
242
+ }
243
+ for (const layer of painter.layers) {
244
+ if (!layer.visible) {
245
+ continue;
246
+ }
247
+ ctx.globalAlpha = layer.opacity;
248
+ ctx.drawImage(layer.canvas, 0, 0, canvas.width, canvas.height);
249
+ }
250
+ ctx.globalAlpha = 1;
251
+ }, [painter.layers, painter.renderVersion, zoom]);
252
+ const screenToTexture = (e) => {
253
+ const rect = canvasRef.current?.getBoundingClientRect();
254
+ if (!rect) {
255
+ return { x: 0, y: 0 };
256
+ }
183
257
  return {
184
- brush,
185
- setBrush,
186
- layers,
187
- addLayer,
188
- removeLayer,
189
- activeLayerId,
190
- setActiveLayerId,
191
- tool,
192
- setTool,
193
- paintStroke,
194
- flattenToBlob,
195
- beginStroke,
196
- undo,
197
- canUndo: undoStackRef.current.length > 0,
198
- toggleLayerVisibility,
199
- setLayerOpacity,
200
- eyedrop,
201
- renderVersion,
258
+ x: (e.clientX - rect.left) / rect.width * textureWidth,
259
+ y: (e.clientY - rect.top) / rect.height * textureHeight
202
260
  };
203
- }
204
- // ── Component ───────────────────────────────────────────────────
205
- /** 3D texture painting tool with UV-space brush painting, zoom/pan, and layers. */
206
- const Nice3DTexturePainter = ({ textureWidth = 1024, textureHeight = 1024, onSave, className = '', }) => {
207
- const { t } = i18n.useNiceTranslation();
208
- const painter = useTexturePainter(textureWidth, textureHeight);
209
- const canvasRef = React.useRef(null);
210
- const isPainting = React.useRef(false);
211
- const [zoom, setZoom] = React.useState(0.5);
212
- const [panOffset, setPanOffset] = React.useState({ x: 0, y: 0 });
213
- const isPanning = React.useRef(false);
214
- const lastPan = React.useRef({ x: 0, y: 0 });
215
- const [cursorPos, setCursorPos] = React.useState(null);
216
- const pw = Math.round(textureWidth * zoom);
217
- const ph = Math.round(textureHeight * zoom);
218
- // Render composite texture to preview
219
- React.useEffect(() => {
220
- const canvas = canvasRef.current;
221
- if (!canvas) {
222
- return;
223
- }
224
- const ctx = canvas.getContext('2d');
225
- if (!ctx) {
226
- return;
227
- }
228
- ctx.clearRect(0, 0, canvas.width, canvas.height);
229
- const size = 8;
230
- // Canvas ignores var(): resolve the checker colours once per paint.
231
- const checkA = uiCore.resolveCssColor('var(--nice-border, #ccc)', canvas);
232
- const checkB = uiCore.resolveCssColor('var(--nice-bg, #fff)', canvas);
233
- for (let y = 0; y < canvas.height; y += size) {
234
- for (let x = 0; x < canvas.width; x += size) {
235
- ctx.fillStyle = ((x + y) / size) % 2 === 0 ? checkA : checkB;
236
- ctx.fillRect(x, y, size, size);
261
+ };
262
+ const handlePointerDown = (e) => {
263
+ if (e.button === 1) {
264
+ isPanning.current = true;
265
+ lastPan.current = { x: e.clientX, y: e.clientY };
266
+ e.preventDefault();
267
+ return;
268
+ }
269
+ if (painter.tool === "eyedropper") {
270
+ const { x: x2, y: y2 } = screenToTexture(e);
271
+ painter.eyedrop(x2, y2);
272
+ return;
273
+ }
274
+ painter.beginStroke();
275
+ isPainting.current = true;
276
+ const { x, y } = screenToTexture(e);
277
+ painter.paintStroke(x, y);
278
+ e.target.setPointerCapture(e.pointerId);
279
+ };
280
+ const handlePointerMove = (e) => {
281
+ const { x, y } = screenToTexture(e);
282
+ setCursorPos({ x: Math.round(x), y: Math.round(y) });
283
+ if (isPanning.current) {
284
+ setPanOffset((prev) => ({
285
+ x: prev.x + e.clientX - lastPan.current.x,
286
+ y: prev.y + e.clientY - lastPan.current.y
287
+ }));
288
+ lastPan.current = { x: e.clientX, y: e.clientY };
289
+ return;
290
+ }
291
+ if (!isPainting.current) {
292
+ return;
293
+ }
294
+ painter.paintStroke(x, y);
295
+ };
296
+ const handlePointerUp = () => {
297
+ isPainting.current = false;
298
+ isPanning.current = false;
299
+ };
300
+ const handleWheel = (e) => {
301
+ e.preventDefault();
302
+ setZoom((z) => Math.max(0.1, Math.min(4, z + (e.deltaY > 0 ? -0.05 : 0.05))));
303
+ };
304
+ const handleSave = async () => {
305
+ const blob = await painter.flattenToBlob();
306
+ if (blob) {
307
+ onSave?.(blob);
308
+ }
309
+ };
310
+ const toolIcons = {
311
+ brush: "brush",
312
+ eraser: "eraser",
313
+ eyedropper: "eyedropper",
314
+ fill: "bucket"
315
+ };
316
+ const btnStyle = (active) => ({
317
+ padding: "4px 8px",
318
+ border: "none",
319
+ borderRadius: 3,
320
+ cursor: "pointer",
321
+ fontSize: 11,
322
+ background: active ? "var(--nice-info, #339af0)" : "var(--nice-bg-secondary, #2c2e33)",
323
+ color: active ? "var(--nice-color-white, #fff)" : "var(--nice-text, #c1c2c5)",
324
+ display: "flex",
325
+ alignItems: "center",
326
+ gap: 4
327
+ });
328
+ const toolCursor = painter.tool === "eyedropper" ? "crosshair" : painter.tool === "brush" ? "crosshair" : painter.tool === "eraser" ? "cell" : "crosshair";
329
+ return /* @__PURE__ */ jsxRuntime.jsxs(
330
+ "div",
331
+ {
332
+ className: `nice-3d-texture-painter ${className}`,
333
+ style: {
334
+ display: "flex",
335
+ gap: 8,
336
+ background: "var(--nice-bg, #1a1b1e)",
337
+ color: "var(--nice-text, #c1c2c5)",
338
+ padding: 12,
339
+ borderRadius: 8
340
+ },
341
+ children: [
342
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { width: 140, fontSize: 11, display: "flex", flexDirection: "column", gap: 4 }, children: [
343
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: t("texturePainter3d.tools", "Tools") }),
344
+ ["brush", "eraser", "eyedropper", "fill"].map((t2) => /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: () => painter.setTool(t2), style: btnStyle(painter.tool === t2), children: [
345
+ /* @__PURE__ */ jsxRuntime.jsx(uiCore.NiceIcon, { name: toolIcons[t2], size: 14 }),
346
+ " ",
347
+ t2.charAt(0).toUpperCase() + t2.slice(1),
348
+ " ",
349
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "var(--nice-text-secondary, #868e96)", marginLeft: "auto" }, children: t2 === "brush" ? "B" : t2 === "eraser" ? "E" : t2 === "eyedropper" ? "I" : "G" })
350
+ ] }, t2)),
351
+ /* @__PURE__ */ jsxRuntime.jsx(
352
+ "hr",
353
+ {
354
+ style: {
355
+ border: "none",
356
+ borderTop: "1px solid var(--nice-border, #373a40)",
357
+ width: "100%",
358
+ margin: "4px 0"
359
+ }
237
360
  }
238
- }
239
- for (const layer of painter.layers) {
240
- if (!layer.visible) {
241
- continue;
361
+ ),
362
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "space-between" }, children: /* @__PURE__ */ jsxRuntime.jsxs(
363
+ "button",
364
+ {
365
+ onClick: () => painter.undo(),
366
+ style: btnStyle(false),
367
+ title: t("texturePainter3d.undoCtrlZ", "Undo (Ctrl+Z)"),
368
+ children: [
369
+ /* @__PURE__ */ jsxRuntime.jsx(uiCore.NiceIcon, { name: "reply", size: 14 }),
370
+ " ",
371
+ t("texturePainter3d.undo", "Undo")
372
+ ]
242
373
  }
243
- ctx.globalAlpha = layer.opacity;
244
- ctx.drawImage(layer.canvas, 0, 0, canvas.width, canvas.height);
245
- }
246
- ctx.globalAlpha = 1;
247
- }, [painter.layers, painter.renderVersion, zoom]);
248
- const screenToTexture = (e) => {
249
- var _a;
250
- const rect = (_a = canvasRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
251
- if (!rect) {
252
- return { x: 0, y: 0 };
253
- }
254
- return {
255
- x: ((e.clientX - rect.left) / rect.width) * textureWidth,
256
- y: ((e.clientY - rect.top) / rect.height) * textureHeight,
257
- };
258
- };
259
- const handlePointerDown = (e) => {
260
- if (e.button === 1) {
261
- isPanning.current = true;
262
- lastPan.current = { x: e.clientX, y: e.clientY };
263
- e.preventDefault();
264
- return;
265
- }
266
- if (painter.tool === 'eyedropper') {
267
- const { x, y } = screenToTexture(e);
268
- painter.eyedrop(x, y);
269
- return;
270
- }
271
- painter.beginStroke();
272
- isPainting.current = true;
273
- const { x, y } = screenToTexture(e);
274
- painter.paintStroke(x, y);
275
- e.target.setPointerCapture(e.pointerId);
276
- };
277
- const handlePointerMove = (e) => {
278
- const { x, y } = screenToTexture(e);
279
- setCursorPos({ x: Math.round(x), y: Math.round(y) });
280
- if (isPanning.current) {
281
- setPanOffset((prev) => ({
282
- x: prev.x + e.clientX - lastPan.current.x,
283
- y: prev.y + e.clientY - lastPan.current.y,
284
- }));
285
- lastPan.current = { x: e.clientX, y: e.clientY };
286
- return;
287
- }
288
- if (!isPainting.current) {
289
- return;
290
- }
291
- painter.paintStroke(x, y);
292
- };
293
- const handlePointerUp = () => {
294
- isPainting.current = false;
295
- isPanning.current = false;
296
- };
297
- const handleWheel = (e) => {
298
- e.preventDefault();
299
- setZoom((z) => Math.max(0.1, Math.min(4, z + (e.deltaY > 0 ? -0.05 : 0.05))));
300
- };
301
- const handleSave = async () => {
302
- const blob = await painter.flattenToBlob();
303
- if (blob) {
304
- onSave === null || onSave === void 0 ? void 0 : onSave(blob);
305
- }
306
- };
307
- const toolIcons = {
308
- brush: 'brush',
309
- eraser: 'eraser',
310
- eyedropper: 'eyedropper',
311
- fill: 'bucket',
312
- };
313
- const btnStyle = (active) => ({
314
- padding: '4px 8px',
315
- border: 'none',
316
- borderRadius: 3,
317
- cursor: 'pointer',
318
- fontSize: 11,
319
- background: active ? 'var(--nice-info, #339af0)' : 'var(--nice-bg-secondary, #2c2e33)',
320
- color: active ? 'var(--nice-color-white, #fff)' : 'var(--nice-text, #c1c2c5)',
321
- display: 'flex',
322
- alignItems: 'center',
323
- gap: 4,
324
- });
325
- const toolCursor = painter.tool === 'eyedropper'
326
- ? 'crosshair'
327
- : painter.tool === 'brush'
328
- ? 'crosshair'
329
- : painter.tool === 'eraser'
330
- ? 'cell'
331
- : 'crosshair';
332
- return (jsxRuntime.jsxs("div", { className: `nice-3d-texture-painter ${className}`, style: {
333
- display: 'flex',
334
- gap: 8,
335
- background: 'var(--nice-bg, #1a1b1e)',
336
- color: 'var(--nice-text, #c1c2c5)',
337
- padding: 12,
338
- borderRadius: 8,
339
- }, children: [jsxRuntime.jsxs("div", { style: { width: 140, fontSize: 11, display: 'flex', flexDirection: 'column', gap: 4 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: t('texturePainter3d.tools', 'Tools') }), ['brush', 'eraser', 'eyedropper', 'fill'].map((t) => (jsxRuntime.jsxs("button", { onClick: () => painter.setTool(t), style: btnStyle(painter.tool === t), children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: toolIcons[t], size: 14 }), " ", t.charAt(0).toUpperCase() + t.slice(1), ' ', jsxRuntime.jsx("span", { style: { color: 'var(--nice-text-secondary, #868e96)', marginLeft: 'auto' }, children: t === 'brush' ? 'B' : t === 'eraser' ? 'E' : t === 'eyedropper' ? 'I' : 'G' })] }, t))), jsxRuntime.jsx("hr", { style: {
340
- border: 'none',
341
- borderTop: '1px solid var(--nice-border, #373a40)',
342
- width: '100%',
343
- margin: '4px 0',
344
- } }), jsxRuntime.jsx("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: jsxRuntime.jsxs("button", { onClick: () => painter.undo(), style: btnStyle(false), title: t('texturePainter3d.undoCtrlZ', 'Undo (Ctrl+Z)'), children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: "reply", size: 14 }), " ", t('texturePainter3d.undo', 'Undo')] }) }), jsxRuntime.jsx("hr", { style: {
345
- border: 'none',
346
- borderTop: '1px solid var(--nice-border, #373a40)',
347
- width: '100%',
348
- margin: '4px 0',
349
- } }), jsxRuntime.jsx("label", { children: t('texturePainter3d.sizePx', 'Size: {size}px').replace('{size}', String(painter.brush.size)) }), jsxRuntime.jsx("input", { type: "range", min: 1, max: 100, value: painter.brush.size, onChange: (e) => painter.setBrush({ ...painter.brush, size: Number(e.target.value) }), style: { width: '100%' } }), jsxRuntime.jsx("label", { children: t('texturePainter3d.opacity', 'Opacity: {opacity}%').replace('{opacity}', String(Math.round(painter.brush.opacity * 100))) }), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: painter.brush.opacity, onChange: (e) => painter.setBrush({ ...painter.brush, opacity: Number(e.target.value) }), style: { width: '100%' } }), jsxRuntime.jsx("label", { children: t('texturePainter3d.hardness', 'Hardness: {hardness}%').replace('{hardness}', String(Math.round(painter.brush.hardness * 100))) }), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: painter.brush.hardness, onChange: (e) => painter.setBrush({ ...painter.brush, hardness: Number(e.target.value) }), style: { width: '100%' } }), jsxRuntime.jsx("label", { children: t('texturePainter3d.blendMode', 'Blend Mode') }), jsxRuntime.jsxs("select", { value: painter.brush.blendMode, onChange: (e) => painter.setBrush({
350
- ...painter.brush,
351
- blendMode: e.target.value,
352
- }), style: {
353
- width: '100%',
354
- padding: 2,
355
- background: 'var(--nice-bg-secondary, #2c2e33)',
356
- color: 'var(--nice-text, #c1c2c5)',
357
- border: '1px solid var(--nice-border, #373a40)',
358
- borderRadius: 3,
359
- fontSize: 10,
360
- }, children: [jsxRuntime.jsx("option", { value: "normal", children: t('texturePainter3d.normal', 'Normal') }), jsxRuntime.jsx("option", { value: "multiply", children: t('texturePainter3d.multiply', 'Multiply') }), jsxRuntime.jsx("option", { value: "screen", children: t('texturePainter3d.screen', 'Screen') }), jsxRuntime.jsx("option", { value: "overlay", children: t('texturePainter3d.overlay', 'Overlay') })] }), jsxRuntime.jsx("label", { children: t('texturePainter3d.color', 'Color') }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4, alignItems: 'center' }, children: [jsxRuntime.jsx("input", { type: "color", value: painter.brush.color, onChange: (e) => painter.setBrush({ ...painter.brush, color: e.target.value }), style: { width: 32, height: 24, border: 'none', cursor: 'pointer' } }), jsxRuntime.jsx("span", { style: { fontFamily: 'monospace', fontSize: 10 }, children: painter.brush.color })] }), jsxRuntime.jsx("hr", { style: {
361
- border: 'none',
362
- borderTop: '1px solid var(--nice-border, #373a40)',
363
- width: '100%',
364
- margin: '4px 0',
365
- } }), jsxRuntime.jsx("label", { children: t('texturePainter3d.zoom', 'Zoom: {zoom}%').replace('{zoom}', String(Math.round(zoom * 100))) }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 2 }, children: [jsxRuntime.jsx("button", { onClick: () => setZoom((z) => Math.max(0.1, z - 0.1)), style: btnStyle(false), children: "\u2212" }), jsxRuntime.jsx("button", { onClick: () => setZoom(0.5), style: btnStyle(false), children: t('texturePainter3d.reset', 'Reset') }), jsxRuntime.jsx("button", { onClick: () => setZoom((z) => Math.min(4, z + 0.1)), style: btnStyle(false), children: "+" })] })] }), jsxRuntime.jsxs("div", { style: {
366
- flex: 1,
367
- overflow: 'hidden',
368
- position: 'relative',
369
- background: 'var(--nice-bg, #141517)',
370
- borderRadius: 6,
371
- }, onWheel: handleWheel, children: [jsxRuntime.jsx("div", { style: {
372
- transform: `translate(${panOffset.x}px, ${panOffset.y}px)`,
373
- display: 'inline-block',
374
- padding: 16,
375
- }, children: jsxRuntime.jsx("canvas", { ref: canvasRef, width: pw, height: ph, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, style: {
376
- cursor: toolCursor,
377
- borderRadius: 4,
378
- border: '1px solid var(--nice-border, #373a40)',
379
- } }) }), jsxRuntime.jsxs("div", { style: {
380
- position: 'absolute',
381
- bottom: 4,
382
- left: 8,
383
- fontSize: 10,
384
- color: 'var(--nice-text-secondary, #868e96)',
385
- display: 'flex',
386
- gap: 12,
387
- }, children: [jsxRuntime.jsxs("span", { children: [textureWidth, "\u00D7", textureHeight] }), cursorPos && (jsxRuntime.jsxs("span", { children: ["X: ", cursorPos.x, " Y: ", cursorPos.y] })), jsxRuntime.jsx("span", { children: t('texturePainter3d.zoom', 'Zoom: {zoom}%').replace('{zoom}', String(Math.round(zoom * 100))) })] })] }), jsxRuntime.jsxs("div", { style: { width: 160, fontSize: 11, display: 'flex', flexDirection: 'column', gap: 4 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: t('texturePainter3d.layers', 'Layers') }), jsxRuntime.jsx("div", { style: { flex: 1, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 2 }, children: [...painter.layers].reverse().map((l) => (jsxRuntime.jsxs("div", { style: {
388
- padding: '4px 6px',
389
- borderRadius: 3,
390
- background: painter.activeLayerId === l.id
391
- ? 'var(--nice-info, #339af0)'
392
- : 'var(--nice-bg-secondary, #2c2e33)',
393
- color: painter.activeLayerId === l.id
394
- ? 'var(--nice-color-white, #fff)'
395
- : 'var(--nice-text, #c1c2c5)',
396
- }, children: [jsxRuntime.jsxs("div", { role: "button", tabIndex: 0, onKeyDown: uiCore.clickOnActivationKey, style: { display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer' }, onClick: () => painter.setActiveLayerId(l.id), children: [jsxRuntime.jsx("button", { onClick: (e) => {
397
- e.stopPropagation();
398
- painter.toggleLayerVisibility(l.id);
399
- }, style: {
400
- background: 'transparent',
401
- border: 'none',
402
- cursor: 'pointer',
403
- padding: 0,
404
- color: l.visible
405
- ? 'var(--nice-text, #c1c2c5)'
406
- : 'var(--nice-text-secondary, #5c5f66)',
407
- fontSize: 12,
408
- }, title: l.visible
409
- ? t('texturePainter3d.hideLayer', 'Hide layer')
410
- : t('texturePainter3d.showLayer', 'Show layer'), "aria-label": l.visible
411
- ? t('texturePainter3d.hideLayer', 'Hide layer')
412
- : t('texturePainter3d.showLayer', 'Show layer'), children: jsxRuntime.jsx(uiCore.NiceIcon, { name: l.visible ? 'eye' : 'eye-off', size: 14 }) }), jsxRuntime.jsx("span", { style: {
413
- flex: 1,
414
- overflow: 'hidden',
415
- textOverflow: 'ellipsis',
416
- whiteSpace: 'nowrap',
417
- }, children: l.name }), l.id !== 'base' && (jsxRuntime.jsx("button", { onClick: (e) => {
418
- e.stopPropagation();
419
- painter.removeLayer(l.id);
420
- }, style: {
421
- background: 'transparent',
422
- border: 'none',
423
- color: 'var(--nice-danger, #ff6b6b)',
424
- cursor: 'pointer',
425
- padding: 0,
426
- fontSize: 10,
427
- }, "aria-label": t('texturePainter3d.removeLayer', 'Remove layer'), title: t('texturePainter3d.removeLayer', 'Remove layer'), children: jsxRuntime.jsx(uiCore.NiceIcon, { name: "close", size: 10 }) }))] }), jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginTop: 2 }, children: [jsxRuntime.jsx("span", { style: { fontSize: 9, color: 'var(--nice-text-secondary, #868e96)' }, children: "\u03B1" }), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: l.opacity, onChange: (e) => painter.setLayerOpacity(l.id, Number(e.target.value)), style: { flex: 1, height: 12 } }), jsxRuntime.jsxs("span", { style: { fontSize: 9, minWidth: 24, textAlign: 'right' }, children: [Math.round(l.opacity * 100), "%"] })] })] }, l.id))) }), jsxRuntime.jsx("button", { onClick: () => painter.addLayer(`Layer ${painter.layers.length + 1}`), style: {
428
- padding: '4px 8px',
429
- border: 'none',
430
- borderRadius: 3,
431
- cursor: 'pointer',
432
- background: 'var(--nice-bg-secondary, #2c2e33)',
433
- color: 'var(--nice-text, #c1c2c5)',
434
- }, children: t('texturePainter3d.addLayer', '+ Add Layer') }), jsxRuntime.jsx("hr", { style: {
435
- border: 'none',
436
- borderTop: '1px solid var(--nice-border, #373a40)',
437
- width: '100%',
438
- margin: '4px 0',
439
- } }), jsxRuntime.jsx("button", { onClick: handleSave, style: {
440
- padding: '6px 8px',
441
- background: 'var(--nice-success, #2f9e44)',
442
- color: 'var(--nice-text-on-primary, #fff)',
443
- border: 'none',
444
- borderRadius: 3,
445
- cursor: 'pointer',
446
- fontWeight: 'bold',
447
- }, children: t('texturePainter3d.saveTexture', 'Save Texture') })] })] }));
374
+ ) }),
375
+ /* @__PURE__ */ jsxRuntime.jsx(
376
+ "hr",
377
+ {
378
+ style: {
379
+ border: "none",
380
+ borderTop: "1px solid var(--nice-border, #373a40)",
381
+ width: "100%",
382
+ margin: "4px 0"
383
+ }
384
+ }
385
+ ),
386
+ /* @__PURE__ */ jsxRuntime.jsx("label", { children: t("texturePainter3d.sizePx", "Size: {size}px").replace(
387
+ "{size}",
388
+ String(painter.brush.size)
389
+ ) }),
390
+ /* @__PURE__ */ jsxRuntime.jsx(
391
+ "input",
392
+ {
393
+ type: "range",
394
+ min: 1,
395
+ max: 100,
396
+ value: painter.brush.size,
397
+ onChange: (e) => painter.setBrush({ ...painter.brush, size: Number(e.target.value) }),
398
+ style: { width: "100%" }
399
+ }
400
+ ),
401
+ /* @__PURE__ */ jsxRuntime.jsx("label", { children: t("texturePainter3d.opacity", "Opacity: {opacity}%").replace(
402
+ "{opacity}",
403
+ String(Math.round(painter.brush.opacity * 100))
404
+ ) }),
405
+ /* @__PURE__ */ jsxRuntime.jsx(
406
+ "input",
407
+ {
408
+ type: "range",
409
+ min: 0,
410
+ max: 1,
411
+ step: 0.01,
412
+ value: painter.brush.opacity,
413
+ onChange: (e) => painter.setBrush({ ...painter.brush, opacity: Number(e.target.value) }),
414
+ style: { width: "100%" }
415
+ }
416
+ ),
417
+ /* @__PURE__ */ jsxRuntime.jsx("label", { children: t("texturePainter3d.hardness", "Hardness: {hardness}%").replace(
418
+ "{hardness}",
419
+ String(Math.round(painter.brush.hardness * 100))
420
+ ) }),
421
+ /* @__PURE__ */ jsxRuntime.jsx(
422
+ "input",
423
+ {
424
+ type: "range",
425
+ min: 0,
426
+ max: 1,
427
+ step: 0.01,
428
+ value: painter.brush.hardness,
429
+ onChange: (e) => painter.setBrush({ ...painter.brush, hardness: Number(e.target.value) }),
430
+ style: { width: "100%" }
431
+ }
432
+ ),
433
+ /* @__PURE__ */ jsxRuntime.jsx("label", { children: t("texturePainter3d.blendMode", "Blend Mode") }),
434
+ /* @__PURE__ */ jsxRuntime.jsxs(
435
+ "select",
436
+ {
437
+ value: painter.brush.blendMode,
438
+ onChange: (e) => painter.setBrush({
439
+ ...painter.brush,
440
+ blendMode: e.target.value
441
+ }),
442
+ style: {
443
+ width: "100%",
444
+ padding: 2,
445
+ background: "var(--nice-bg-secondary, #2c2e33)",
446
+ color: "var(--nice-text, #c1c2c5)",
447
+ border: "1px solid var(--nice-border, #373a40)",
448
+ borderRadius: 3,
449
+ fontSize: 10
450
+ },
451
+ children: [
452
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "normal", children: t("texturePainter3d.normal", "Normal") }),
453
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "multiply", children: t("texturePainter3d.multiply", "Multiply") }),
454
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "screen", children: t("texturePainter3d.screen", "Screen") }),
455
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "overlay", children: t("texturePainter3d.overlay", "Overlay") })
456
+ ]
457
+ }
458
+ ),
459
+ /* @__PURE__ */ jsxRuntime.jsx("label", { children: t("texturePainter3d.color", "Color") }),
460
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 4, alignItems: "center" }, children: [
461
+ /* @__PURE__ */ jsxRuntime.jsx(
462
+ "input",
463
+ {
464
+ type: "color",
465
+ value: painter.brush.color,
466
+ onChange: (e) => painter.setBrush({ ...painter.brush, color: e.target.value }),
467
+ style: { width: 32, height: 24, border: "none", cursor: "pointer" }
468
+ }
469
+ ),
470
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: "monospace", fontSize: 10 }, children: painter.brush.color })
471
+ ] }),
472
+ /* @__PURE__ */ jsxRuntime.jsx(
473
+ "hr",
474
+ {
475
+ style: {
476
+ border: "none",
477
+ borderTop: "1px solid var(--nice-border, #373a40)",
478
+ width: "100%",
479
+ margin: "4px 0"
480
+ }
481
+ }
482
+ ),
483
+ /* @__PURE__ */ jsxRuntime.jsx("label", { children: t("texturePainter3d.zoom", "Zoom: {zoom}%").replace(
484
+ "{zoom}",
485
+ String(Math.round(zoom * 100))
486
+ ) }),
487
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 2 }, children: [
488
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setZoom((z) => Math.max(0.1, z - 0.1)), style: btnStyle(false), children: "−" }),
489
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setZoom(0.5), style: btnStyle(false), children: t("texturePainter3d.reset", "Reset") }),
490
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setZoom((z) => Math.min(4, z + 0.1)), style: btnStyle(false), children: "+" })
491
+ ] })
492
+ ] }),
493
+ /* @__PURE__ */ jsxRuntime.jsxs(
494
+ "div",
495
+ {
496
+ style: {
497
+ flex: 1,
498
+ overflow: "hidden",
499
+ position: "relative",
500
+ background: "var(--nice-bg, #141517)",
501
+ borderRadius: 6
502
+ },
503
+ onWheel: handleWheel,
504
+ children: [
505
+ /* @__PURE__ */ jsxRuntime.jsx(
506
+ "div",
507
+ {
508
+ style: {
509
+ transform: `translate(${panOffset.x}px, ${panOffset.y}px)`,
510
+ display: "inline-block",
511
+ padding: 16
512
+ },
513
+ children: /* @__PURE__ */ jsxRuntime.jsx(
514
+ "canvas",
515
+ {
516
+ ref: canvasRef,
517
+ width: pw,
518
+ height: ph,
519
+ onPointerDown: handlePointerDown,
520
+ onPointerMove: handlePointerMove,
521
+ onPointerUp: handlePointerUp,
522
+ style: {
523
+ cursor: toolCursor,
524
+ borderRadius: 4,
525
+ border: "1px solid var(--nice-border, #373a40)"
526
+ }
527
+ }
528
+ )
529
+ }
530
+ ),
531
+ /* @__PURE__ */ jsxRuntime.jsxs(
532
+ "div",
533
+ {
534
+ style: {
535
+ position: "absolute",
536
+ bottom: 4,
537
+ left: 8,
538
+ fontSize: 10,
539
+ color: "var(--nice-text-secondary, #868e96)",
540
+ display: "flex",
541
+ gap: 12
542
+ },
543
+ children: [
544
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
545
+ textureWidth,
546
+ "×",
547
+ textureHeight
548
+ ] }),
549
+ cursorPos && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
550
+ "X: ",
551
+ cursorPos.x,
552
+ " Y: ",
553
+ cursorPos.y
554
+ ] }),
555
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("texturePainter3d.zoom", "Zoom: {zoom}%").replace(
556
+ "{zoom}",
557
+ String(Math.round(zoom * 100))
558
+ ) })
559
+ ]
560
+ }
561
+ )
562
+ ]
563
+ }
564
+ ),
565
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { width: 160, fontSize: 11, display: "flex", flexDirection: "column", gap: 4 }, children: [
566
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: t("texturePainter3d.layers", "Layers") }),
567
+ /* @__PURE__ */ jsxRuntime.jsx(
568
+ "div",
569
+ {
570
+ style: { flex: 1, overflowY: "auto", display: "flex", flexDirection: "column", gap: 2 },
571
+ children: [...painter.layers].reverse().map((l) => /* @__PURE__ */ jsxRuntime.jsxs(
572
+ "div",
573
+ {
574
+ style: {
575
+ padding: "4px 6px",
576
+ borderRadius: 3,
577
+ background: painter.activeLayerId === l.id ? "var(--nice-info, #339af0)" : "var(--nice-bg-secondary, #2c2e33)",
578
+ color: painter.activeLayerId === l.id ? "var(--nice-color-white, #fff)" : "var(--nice-text, #c1c2c5)"
579
+ },
580
+ children: [
581
+ /* @__PURE__ */ jsxRuntime.jsxs(
582
+ "div",
583
+ {
584
+ role: "button",
585
+ tabIndex: 0,
586
+ onKeyDown: uiCore.clickOnActivationKey,
587
+ style: { display: "flex", alignItems: "center", gap: 4, cursor: "pointer" },
588
+ onClick: () => painter.setActiveLayerId(l.id),
589
+ children: [
590
+ /* @__PURE__ */ jsxRuntime.jsx(
591
+ "button",
592
+ {
593
+ onClick: (e) => {
594
+ e.stopPropagation();
595
+ painter.toggleLayerVisibility(l.id);
596
+ },
597
+ style: {
598
+ background: "transparent",
599
+ border: "none",
600
+ cursor: "pointer",
601
+ padding: 0,
602
+ color: l.visible ? "var(--nice-text, #c1c2c5)" : "var(--nice-text-secondary, #5c5f66)",
603
+ fontSize: 12
604
+ },
605
+ title: l.visible ? t("texturePainter3d.hideLayer", "Hide layer") : t("texturePainter3d.showLayer", "Show layer"),
606
+ "aria-label": l.visible ? t("texturePainter3d.hideLayer", "Hide layer") : t("texturePainter3d.showLayer", "Show layer"),
607
+ children: /* @__PURE__ */ jsxRuntime.jsx(uiCore.NiceIcon, { name: l.visible ? "eye" : "eye-off", size: 14 })
608
+ }
609
+ ),
610
+ /* @__PURE__ */ jsxRuntime.jsx(
611
+ "span",
612
+ {
613
+ style: {
614
+ flex: 1,
615
+ overflow: "hidden",
616
+ textOverflow: "ellipsis",
617
+ whiteSpace: "nowrap"
618
+ },
619
+ children: l.name
620
+ }
621
+ ),
622
+ l.id !== "base" && /* @__PURE__ */ jsxRuntime.jsx(
623
+ "button",
624
+ {
625
+ onClick: (e) => {
626
+ e.stopPropagation();
627
+ painter.removeLayer(l.id);
628
+ },
629
+ style: {
630
+ background: "transparent",
631
+ border: "none",
632
+ color: "var(--nice-danger, #ff6b6b)",
633
+ cursor: "pointer",
634
+ padding: 0,
635
+ fontSize: 10
636
+ },
637
+ "aria-label": t("texturePainter3d.removeLayer", "Remove layer"),
638
+ title: t("texturePainter3d.removeLayer", "Remove layer"),
639
+ children: /* @__PURE__ */ jsxRuntime.jsx(uiCore.NiceIcon, { name: "close", size: 10 })
640
+ }
641
+ )
642
+ ]
643
+ }
644
+ ),
645
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4, marginTop: 2 }, children: [
646
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 9, color: "var(--nice-text-secondary, #868e96)" }, children: "α" }),
647
+ /* @__PURE__ */ jsxRuntime.jsx(
648
+ "input",
649
+ {
650
+ type: "range",
651
+ min: 0,
652
+ max: 1,
653
+ step: 0.01,
654
+ value: l.opacity,
655
+ onChange: (e) => painter.setLayerOpacity(l.id, Number(e.target.value)),
656
+ style: { flex: 1, height: 12 }
657
+ }
658
+ ),
659
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: 9, minWidth: 24, textAlign: "right" }, children: [
660
+ Math.round(l.opacity * 100),
661
+ "%"
662
+ ] })
663
+ ] })
664
+ ]
665
+ },
666
+ l.id
667
+ ))
668
+ }
669
+ ),
670
+ /* @__PURE__ */ jsxRuntime.jsx(
671
+ "button",
672
+ {
673
+ onClick: () => painter.addLayer(`Layer ${painter.layers.length + 1}`),
674
+ style: {
675
+ padding: "4px 8px",
676
+ border: "none",
677
+ borderRadius: 3,
678
+ cursor: "pointer",
679
+ background: "var(--nice-bg-secondary, #2c2e33)",
680
+ color: "var(--nice-text, #c1c2c5)"
681
+ },
682
+ children: t("texturePainter3d.addLayer", "+ Add Layer")
683
+ }
684
+ ),
685
+ /* @__PURE__ */ jsxRuntime.jsx(
686
+ "hr",
687
+ {
688
+ style: {
689
+ border: "none",
690
+ borderTop: "1px solid var(--nice-border, #373a40)",
691
+ width: "100%",
692
+ margin: "4px 0"
693
+ }
694
+ }
695
+ ),
696
+ /* @__PURE__ */ jsxRuntime.jsx(
697
+ "button",
698
+ {
699
+ onClick: handleSave,
700
+ style: {
701
+ padding: "6px 8px",
702
+ background: "var(--nice-success, #2f9e44)",
703
+ color: "var(--nice-text-on-primary, #fff)",
704
+ border: "none",
705
+ borderRadius: 3,
706
+ cursor: "pointer",
707
+ fontWeight: "bold"
708
+ },
709
+ children: t("texturePainter3d.saveTexture", "Save Texture")
710
+ }
711
+ )
712
+ ] })
713
+ ]
714
+ }
715
+ );
448
716
  };
449
-
450
717
  exports.Nice3DTexturePainter = Nice3DTexturePainter;
451
718
  exports.useTexturePainter = useTexturePainter;