diff --git a/Sources/arm/Context.hx b/Sources/arm/Context.hx index efc15207..cf10f140 100644 --- a/Sources/arm/Context.hx +++ b/Sources/arm/Context.hx @@ -61,17 +61,8 @@ class Context { public static var viewIndexLast = -1; public static var materialPreview = false; // Drawing material previews public static var savedCamera = Mat4.identity(); - public static var baseRPicked = 0.0; - public static var baseGPicked = 0.0; - public static var baseBPicked = 0.0; - public static var normalRPicked = 0.0; - public static var normalGPicked = 0.0; - public static var normalBPicked = 0.0; - public static var opacityPicked = 0.0; - public static var roughnessPicked = 0.0; - public static var metallicPicked = 0.0; - public static var occlusionPicked = 0.0; - public static var heightPicked = 0.0; + + public static var swatch: TSwatch; public static var materialIdPicked = 0; public static var uvxPicked = 0.0; public static var uvyPicked = 0.0; @@ -318,6 +309,15 @@ class Context { UIView2D.inst.hwnd.redraws = 2; } + public static function setSwatch(s: TSwatch) { + swatch = s; + App.notifyOnNextFrame(function() { + MakeMaterial.parsePaintMaterial(); + RenderUtil.makeMaterialPreview(); + UISidebar.inst.hwnd1.redraws = 2; + }); + } + public static function setLayer(l: LayerSlot, isMask = false) { if (l == layer && layerIsMask == isMask) return; layer = l; diff --git a/Sources/arm/Project.hx b/Sources/arm/Project.hx index d66cbe27..e79c0d47 100644 --- a/Sources/arm/Project.hx +++ b/Sources/arm/Project.hx @@ -253,6 +253,8 @@ class Project { var fontNames = App.font.getFontNames(); fonts = [new FontSlot(fontNames.length > 0 ? fontNames[0] : "default.ttf", App.font)]; Context.font = fonts[0]; + Project.raw.swatches = [makeSwatch()]; + Context.swatch = Project.raw.swatches[0]; History.reset(); @@ -463,6 +465,16 @@ class Project { for (pa in packed_assets) if (pa.name == name) return true; return false; } + + public static function exportSwatches() { + UIFiles.show("arm", true, function(path: String) { + ExportArm.runSwatches(path); + }); + } + + public static function makeSwatch(): TSwatch { + return { base: 0xffffffff, opacity: 1.0, occlusion: 1.0, roughness: 0.0, metallic: 0.0, normal: 0xff8080ff, emission: 0.0, height: 0.0, subsurface: 0.0 }; + } } typedef TNodeGroup = { diff --git a/Sources/arm/ProjectFormat.hx b/Sources/arm/ProjectFormat.hx index 8fa8807a..a5c35d7e 100644 --- a/Sources/arm/ProjectFormat.hx +++ b/Sources/arm/ProjectFormat.hx @@ -17,6 +17,7 @@ typedef TProjectFormat = { @:optional public var mesh_assets: Array; @:optional public var atlas_objects: Array; @:optional public var atlas_names: Array; + @:optional public var swatches: Array; @:optional public var is_bgra: Null; // Swapped red and blue channels for layer textures @:optional public var packed_assets: Array; } @@ -62,3 +63,15 @@ typedef TPackedAsset = { public var name: String; public var bytes: haxe.io.Bytes; } + +typedef TSwatch = { + public var base: kha.Color; + public var opacity: Float; + public var occlusion: Float; + public var roughness: Float; + public var metallic: Float; + public var normal: kha.Color; + public var emission: Float; + public var height: Float; + public var subsurface: Float; +} diff --git a/Sources/arm/io/ExportArm.hx b/Sources/arm/io/ExportArm.hx index 6197371a..51aec34d 100644 --- a/Sources/arm/io/ExportArm.hx +++ b/Sources/arm/io/ExportArm.hx @@ -102,6 +102,7 @@ class ExportArm { mesh_assets: mesh_files, atlas_objects: Project.atlasObjects, atlas_names: Project.atlasNames, + swatches: Project.raw.swatches, packed_assets: packed_assets, #if (kha_metal || kha_vulkan) is_bgra: true @@ -347,4 +348,14 @@ class ExportArm { for (image in tempImages) image.unload(); }); } + + public static function runSwatches(path: String) { + if (!path.endsWith(".arm")) path += ".arm"; + var raw = { + version: Main.version, + swatches: Project.raw.swatches + }; + var bytes = ArmPack.encode(raw); + Krom.fileSaveBytes(path, bytes.getData(), bytes.length + 1); + } } diff --git a/Sources/arm/io/ImportArm.hx b/Sources/arm/io/ImportArm.hx index 6611abed..1527617a 100644 --- a/Sources/arm/io/ImportArm.hx +++ b/Sources/arm/io/ImportArm.hx @@ -75,6 +75,10 @@ class ImportArm { else if (project.brush_nodes != null) { runBrushFromProject(project, path); } + // Import as swatches + else if (project.swatches != null) { + runSwatchesFromProject(project, path); + } return; } @@ -465,6 +469,14 @@ class ImportArm { Data.deleteBlob(path); } + public static function runSwatchesFromProject(project: TProjectFormat, path: String) { + for (s in project.swatches) { + Project.raw.swatches.push(s); + } + UISidebar.inst.hwnd2.redraws = 2; + Data.deleteBlob(path); + } + static function makePink(abs: String) { Log.error(Strings.error2() + " " + abs); var b = Bytes.alloc(4); diff --git a/Sources/arm/render/RenderPathPaint.hx b/Sources/arm/render/RenderPathPaint.hx index 3aaf2f18..500c222a 100644 --- a/Sources/arm/render/RenderPathPaint.hx +++ b/Sources/arm/render/RenderPathPaint.hx @@ -9,6 +9,7 @@ import iron.Scene; import arm.util.ViewportUtil; import arm.ui.UIView2D; import arm.ui.UIHeader; +import arm.ui.UISidebar; import arm.node.MakeMaterial; import arm.Enums; @@ -177,6 +178,7 @@ class RenderPathPaint { path.bindTarget("texpaint_pack" + tid, "texpaint_pack"); path.drawMeshes("paint"); UIHeader.inst.headerHandle.redraws = 2; + UISidebar.inst.hwnd2.redraws = 2; var texpaint_picker = path.renderTargets.get("texpaint_picker").image; var texpaint_nor_picker = path.renderTargets.get("texpaint_nor_picker").image; @@ -186,17 +188,17 @@ class RenderPathPaint { var c = texpaint_pack_picker.getPixels(); // Picked surface values - Context.baseRPicked = a.get(0) / 255; - Context.baseGPicked = a.get(1) / 255; - Context.baseBPicked = a.get(2) / 255; + Context.swatch.base.Rb = a.get(0); + Context.swatch.base.Gb = a.get(1); + Context.swatch.base.Bb = a.get(2); Context.uvxPicked = a.get(3) / 255; - Context.normalRPicked = b.get(0) / 255; - Context.normalGPicked = b.get(1) / 255; - Context.normalBPicked = b.get(2) / 255; + Context.swatch.normal.Rb = b.get(0); + Context.swatch.normal.Gb = b.get(1); + Context.swatch.normal.Bb = b.get(2); Context.uvyPicked = c.get(3) / 255; - Context.occlusionPicked = c.get(0) / 255; - Context.roughnessPicked = c.get(1) / 255; - Context.metallicPicked = c.get(2) / 255; + Context.swatch.occlusion = c.get(0) / 255; + Context.swatch.roughness = c.get(1) / 255; + Context.swatch.metallic = c.get(2) / 255; // Pick material if (Context.pickerSelectMaterial) { var matid = b.get(3); diff --git a/Sources/arm/render/Uniforms.hx b/Sources/arm/render/Uniforms.hx index 6741c3dd..3786cffe 100644 --- a/Sources/arm/render/Uniforms.hx +++ b/Sources/arm/render/Uniforms.hx @@ -101,19 +101,19 @@ class Uniforms { return Context.layer.decalMat.getScale().z * 0.5; } case "_pickerOpacity": { - return Context.opacityPicked; + return Context.swatch.opacity; } case "_pickerOcclusion": { - return Context.occlusionPicked; + return Context.swatch.occlusion; } case "_pickerRoughness": { - return Context.roughnessPicked; + return Context.swatch.roughness; } case "_pickerMetallic": { - return Context.metallicPicked; + return Context.swatch.metallic; } case "_pickerHeight": { - return Context.heightPicked; + return Context.swatch.height; } } if (MaterialParser.script_links != null) { @@ -198,12 +198,12 @@ class Uniforms { } case "_pickerBase": { v = iron.object.Uniforms.helpVec; - v.set(Context.baseRPicked, Context.baseGPicked, Context.baseBPicked); + v.set(Context.swatch.base.R, Context.swatch.base.G, Context.swatch.base.B); return v; } case "_pickerNormal": { v = iron.object.Uniforms.helpVec; - v.set(Context.normalRPicked, Context.normalGPicked, Context.normalBPicked); + v.set(Context.swatch.normal.R, Context.swatch.normal.G, Context.swatch.normal.B); return v; } } diff --git a/Sources/arm/ui/TabMeshes.hx b/Sources/arm/ui/TabMeshes.hx index 77fad4b9..9b748ad1 100644 --- a/Sources/arm/ui/TabMeshes.hx +++ b/Sources/arm/ui/TabMeshes.hx @@ -60,6 +60,14 @@ class TabMeshes { var h = Id.handle(); h.selected = o.visible; o.visible = ui.check(h, o.name); + if (ui.isHovered && ui.inputReleasedR) { + UIMenu.draw(function(ui: Zui) { + ui.text(o.name, Right, ui.t.HIGHLIGHT_COL); + if (ui.button(tr("Export"), Left)) { + BoxExport.showMesh(); + } + }, 2); + } if (h.changed) { var visibles: Array = []; for (p in Project.paintObjects) if (p.visible) visibles.push(p); diff --git a/Sources/arm/ui/TabSwatches.hx b/Sources/arm/ui/TabSwatches.hx new file mode 100644 index 00000000..812121c3 --- /dev/null +++ b/Sources/arm/ui/TabSwatches.hx @@ -0,0 +1,110 @@ +package arm.ui; + +import zui.Zui; +import zui.Id; +import iron.system.Time; +import iron.system.Input; +import arm.io.ImportFont; +import arm.util.RenderUtil; +import arm.Enums; + +class TabSwatches { + + static var empty: kha.Image = null; + + @:access(zui.Zui) + public static function draw() { + var ui = UISidebar.inst.ui; + if (ui.tab(UISidebar.inst.htab2, tr("Swatches"))) { + + ui.beginSticky(); + ui.row([1 / 4, 1 / 4, 1 / 4]); + + if (ui.button(tr("New"))) { + Context.setSwatch(Project.makeSwatch()); + Project.raw.swatches.push(Context.swatch); + } + if (ui.button(tr("Import"))) Project.importAsset("arm"); + if (ui.isHovered) ui.tooltip(tr("Import swatches") + ' (${Config.keymap.file_import_assets})'); + if (ui.button(tr("Tools..."))) { + UIMenu.draw(function(ui: Zui) { + ui.text(tr("Tools"), Right, ui.t.HIGHLIGHT_COL); + if (ui.button(tr("Clear"), Left)) { + Project.raw.swatches = [Project.makeSwatch()]; + } + }, 3); + } + + ui.endSticky(); + ui.separator(3, false); + + var slotw = Std.int(26 * ui.SCALE()); + var num = Std.int(Config.raw.layout[LayoutSidebarW] / (slotw + 3)); + + for (row in 0...Std.int(Math.ceil(Project.raw.swatches.length / num))) { + ui.row([for (i in 0...num) 1 / num]); + + ui._x += 2; + if (row > 0) ui._y += 6; + + for (j in 0...num) { + var imgw = Std.int(20 * ui.SCALE()); + var i = j + row * num; + if (i >= Project.raw.swatches.length) { + @:privateAccess ui.endElement(imgw); + continue; + } + + if (Context.swatch == Project.raw.swatches[i]) { + var off = row % 2 == 1 ? 1 : 0; + var w = 32; + if (Config.raw.window_scale > 1) w += Std.int(Config.raw.window_scale * 2); + ui.fill(-2, -2, w, w, ui.t.HIGHLIGHT_COL); + } + + if (empty == null) { + var b = haxe.io.Bytes.alloc(4); + b.set(0, 255); + b.set(1, 255); + b.set(2, 255); + b.set(3, 255); + empty = kha.Image.fromBytes(b, 1, 1); + } + var state = ui.image(empty, Project.raw.swatches[i].base, slotw); + + if (state == State.Started) { + Context.setSwatch(Project.raw.swatches[i]); + } + else if (state == State.Released) { + if (Time.time() - Context.selectTime < 0.25) { + + UIMenu.draw(function(ui) { + ui.fill(0, 0, ui._w / ui.ops.scaleFactor, ui.t.ELEMENT_H * 9, ui.t.SEPARATOR_COL); + ui.changed = false; + var h = Id.handle(); + h.color = Context.swatch.base; + Context.swatch.base = zui.Ext.colorWheel(ui, h, false, null, false); + if (ui.changed) UIMenu.keepOpen = true; + if (ui.inputReleased) Context.setSwatch(Context.swatch); // Trigger material preview update + }, 3, Std.int(Input.getMouse().x) - 200, Std.int(Input.getMouse().y) - 250); + } + + Context.selectTime = Time.time(); + } + if (ui.isHovered && ui.inputReleasedR) { + var add = Project.raw.swatches.length > 1 ? 1 : 0; + UIMenu.draw(function(ui: Zui) { + ui.text(tr("Swatch"), Right, ui.t.HIGHLIGHT_COL); + if (Project.raw.swatches.length > 1 && ui.button(tr("Delete"), Left)) { + Context.setSwatch(Project.raw.swatches[i == 0 ? 1 : 0]); + Project.raw.swatches.splice(i, 1); + UISidebar.inst.hwnd2.redraws = 2; + } + }, 1 + add); + } + if (ui.isHovered) ui.tooltip(tr("Swatch")); + } + } + } + } +} diff --git a/Sources/arm/ui/UIHeader.hx b/Sources/arm/ui/UIHeader.hx index 704a0e93..ab40d9a9 100644 --- a/Sources/arm/ui/UIHeader.hx +++ b/Sources/arm/ui/UIHeader.hx @@ -44,24 +44,15 @@ class UIHeader { if (Project.assets.length > 0) ui.image(Project.getImage(Project.assets[cid])); } else if (Context.tool == ToolPicker) { - Context.baseRPicked = Math.round(Context.baseRPicked * 10) / 10; - Context.baseGPicked = Math.round(Context.baseGPicked * 10) / 10; - Context.baseBPicked = Math.round(Context.baseBPicked * 10) / 10; - Context.normalRPicked = Math.round(Context.normalRPicked * 10) / 10; - Context.normalGPicked = Math.round(Context.normalGPicked * 10) / 10; - Context.normalBPicked = Math.round(Context.normalBPicked * 10) / 10; - Context.occlusionPicked = Math.round(Context.occlusionPicked * 100) / 100; - Context.roughnessPicked = Math.round(Context.roughnessPicked * 100) / 100; - Context.metallicPicked = Math.round(Context.metallicPicked * 100) / 100; - var baseRPicked = Context.baseRPicked; - var baseGPicked = Context.baseGPicked; - var baseBPicked = Context.baseBPicked; - var normalRPicked = Context.normalRPicked; - var normalGPicked = Context.normalGPicked; - var normalBPicked = Context.normalBPicked; - var occlusionPicked = Context.occlusionPicked; - var roughnessPicked = Context.roughnessPicked; - var metallicPicked = Context.metallicPicked; + var baseRPicked = Math.round(Context.swatch.base.R * 10) / 10; + var baseGPicked = Math.round(Context.swatch.base.G * 10) / 10; + var baseBPicked = Math.round(Context.swatch.base.B * 10) / 10; + var normalRPicked = Math.round(Context.swatch.normal.R * 10) / 10; + var normalGPicked = Math.round(Context.swatch.normal.G * 10) / 10; + var normalBPicked = Math.round(Context.swatch.normal.B * 10) / 10; + var occlusionPicked = Math.round(Context.swatch.occlusion * 100) / 100; + var roughnessPicked = Math.round(Context.swatch.roughness * 100) / 100; + var metallicPicked = Math.round(Context.swatch.metallic * 100) / 100; #if kha_metal ui.text('TODO'); // Skips first draw #end diff --git a/Sources/arm/ui/UIMenu.hx b/Sources/arm/ui/UIMenu.hx index adce0412..9af3475e 100644 --- a/Sources/arm/ui/UIMenu.hx +++ b/Sources/arm/ui/UIMenu.hx @@ -54,7 +54,7 @@ class UIMenu { } else { var menuItems = [ - 16, // MenuFile + 18, // MenuFile 4, // MenuEdit #if (krom_windows || krom_linux) 14 #else 13 #end, // MenuViewport #if (kha_direct3d12 || kha_vulkan) 13 #else 12 #end, // MenuMode @@ -76,6 +76,7 @@ class UIMenu { if (ui.button(" " + tr("Import Font..."), Left)) Project.importAsset("ttf,ttc,otf"); if (ui.button(" " + tr("Import Material..."), Left)) Project.importMaterial(); if (ui.button(" " + tr("Import Brush..."), Left)) Project.importBrush(); + if (ui.button(" " + tr("Import Swatches..."), Left)) Project.importAsset("arm"); if (ui.button(" " + tr("Import Mesh..."), Left)) Project.importMesh(); if (ui.button(" " + tr("Reimport Mesh"), Left, Config.keymap.file_reimport_mesh)) Project.reimportMesh(); if (ui.button(" " + tr("Reimport Textures"), Left, Config.keymap.file_reimport_textures)) Project.reimportTextures(); @@ -84,6 +85,7 @@ class UIMenu { Context.layersExport = ExportVisible; BoxExport.showTextures(); } + if (ui.button(" " + tr("Export Swatches..."), Left)) Project.exportSwatches(); if (ui.button(" " + tr("Export Mesh..."), Left)) BoxExport.showMesh(); if (ui.button(" " + tr("Bake Material..."), Left)) BoxExport.showBakeMaterial(); ui.fill(0, 0, sepw, 1, ui.t.ACCENT_SELECT_COL); diff --git a/Sources/arm/ui/UISidebar.hx b/Sources/arm/ui/UISidebar.hx index f75db495..28008af1 100644 --- a/Sources/arm/ui/UISidebar.hx +++ b/Sources/arm/ui/UISidebar.hx @@ -89,6 +89,12 @@ class UISidebar { Context.layer = Project.layers[0]; } + if (Project.raw.swatches == null) { + Project.raw.swatches = []; + Project.raw.swatches.push(Project.makeSwatch()); + Context.swatch = Project.raw.swatches[0]; + } + if (Context.emptyEnvmap == null) { var b = Bytes.alloc(4); b.set(0, 3); @@ -682,6 +688,7 @@ class UISidebar { TabTextures.draw(); TabMeshes.draw(); TabFonts.draw(); + TabSwatches.draw(); } ui.end();