From 5914db3bb4491f686428064f96f5f5509b5f70b1 Mon Sep 17 00:00:00 2001
From: rgantzos <86856959+rgantzos@users.noreply.github.com>
Date: Mon, 2 Sep 2024 14:01:09 -0700
Subject: [PATCH] More Paint Functions

---
 features/features.json                        |   5 +
 features/more-editor-fonts/script.js          |   1 +
 features/more-paint-functions/data.json       |  22 +++
 .../more-paint-functions/icons/exclude.svg    |  13 ++
 .../more-paint-functions/icons/intersect.svg  |  20 +++
 .../more-paint-functions/icons/subtract.svg   |  17 ++
 features/more-paint-functions/icons/unite.svg |  13 ++
 features/more-paint-functions/script.js       | 161 ++++++++++++++++++
 features/more-paint-functions/style.css       |   7 +
 9 files changed, 259 insertions(+)
 create mode 100644 features/more-paint-functions/data.json
 create mode 100644 features/more-paint-functions/icons/exclude.svg
 create mode 100644 features/more-paint-functions/icons/intersect.svg
 create mode 100644 features/more-paint-functions/icons/subtract.svg
 create mode 100644 features/more-paint-functions/icons/unite.svg
 create mode 100644 features/more-paint-functions/script.js
 create mode 100644 features/more-paint-functions/style.css

diff --git a/features/features.json b/features/features.json
index 9f0054a1..9588ccbe 100644
--- a/features/features.json
+++ b/features/features.json
@@ -1,4 +1,9 @@
 [
+  {
+    "version": 2,
+    "id": "more-paint-functions",
+    "versionAdded": "v4.0.0"
+  },
   {
     "version": 2,
     "id": "asset-size",
diff --git a/features/more-editor-fonts/script.js b/features/more-editor-fonts/script.js
index 791529dc..7efad503 100644
--- a/features/more-editor-fonts/script.js
+++ b/features/more-editor-fonts/script.js
@@ -9,6 +9,7 @@ export default async function ({ feature, console }) {
   feature.page.waitForElements(
     "div[class^='asset-panel_wrapper_'] div[class^='action-menu_more-buttons_']",
     function (menu) {
+      if (feature.traps.gui().editorTab.activeTabIndex !== 1) return;
       if (menu.querySelector(".ste-more-fonts")) return;
 
       let div = document.createElement("div");
diff --git a/features/more-paint-functions/data.json b/features/more-paint-functions/data.json
new file mode 100644
index 00000000..11c00e90
--- /dev/null
+++ b/features/more-paint-functions/data.json
@@ -0,0 +1,22 @@
+{
+  "title": "More Paint Functions",
+  "description": "Adds new functions to the paint editor. The new functions include unite (combining them into one), subtract (removing one shape from another), exclude (removing the overlap of 2 items), and intersect (removing everything but the overlap of 2 items). Each function only works with 2 items.",
+  "credits": [
+    { "username": "rgantzos", "url": "https://scratch.mit.edu/users/rgantzos/" }
+  ],
+  "type": ["Editor"],
+  "tags": ["New", "Featured"],
+  "dynamic": true,
+  "scripts": [{ "file": "script.js", "runOn": "/projects/*" }],
+  "styles": [{ "file": "style.css", "runOn": "/projects/*" }],
+  "resources": [
+    { "name": "function-exclude", "path": "/icons/exclude.svg" },
+    { "name": "function-intersect", "path": "/icons/intersect.svg" },
+    { "name": "function-subtract", "path": "/icons/subtract.svg" },
+    { "name": "function-unite", "path": "/icons/unite.svg" }
+  ],
+  "components": [{
+    "type": "warning",
+    "content": "In order to avoid clutter in the paint editor, this feature replaces the Copy, Paste and Delete buttons. However, hotkeys still work."
+  }]
+}
diff --git a/features/more-paint-functions/icons/exclude.svg b/features/more-paint-functions/icons/exclude.svg
new file mode 100644
index 00000000..76c50759
--- /dev/null
+++ b/features/more-paint-functions/icons/exclude.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 13">
+  <defs>
+    <style>
+      .cls-1 {
+        fill: #7860aa;
+        fill-rule: evenodd;
+      }
+    </style>
+  </defs>
+  <path class="cls-1" d="M13,1.08v6.85c0,.6-.47,1.07-1.07,1.07h-2.94v-3.92c0-.59-.48-1.08-1.07-1.08h-3.91V1.07c0-.6.48-1.07,1.07-1.07h6.85c.6,0,1.07.49,1.07,1.08Z"/>
+  <path class="cls-1" d="M8.99,9v2.93c0,.6-.48,1.07-1.07,1.07H1.07c-.6,0-1.07-.49-1.07-1.08v-6.85c0-.6.47-1.07,1.07-1.07h2.94v3.92c0,.59.48,1.08,1.07,1.08h3.91Z"/>
+</svg>
\ No newline at end of file
diff --git a/features/more-paint-functions/icons/intersect.svg b/features/more-paint-functions/icons/intersect.svg
new file mode 100644
index 00000000..399d3a7d
--- /dev/null
+++ b/features/more-paint-functions/icons/intersect.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 13">
+  <defs>
+    <style>
+      .cls-1 {
+        opacity: .5;
+      }
+
+      .cls-2 {
+        fill: #7860aa;
+        fill-rule: evenodd;
+      }
+    </style>
+  </defs>
+  <g class="cls-1">
+    <path class="cls-2" d="M11.93,0h-6.85c-.6,0-1.07.47-1.07,1.07h0v2.93h3.91c.6,0,1.07.49,1.07,1.08v3.92h2.94c.6,0,1.07-.47,1.07-1.07h0V1.08c0-.6-.47-1.08-1.07-1.08Z"/>
+    <path class="cls-2" d="M5.08,9c-.6,0-1.07-.49-1.07-1.08v-3.92H1.07c-.6,0-1.07.47-1.07,1.07h0v6.85c0,.6.47,1.08,1.07,1.08h6.85c.6,0,1.07-.47,1.07-1.07h0v-2.93h-3.91Z"/>
+  </g>
+  <path class="cls-2" d="M8.99,6v-.92c0-.6-.47-1.08-1.07-1.08h-3.91v3.92c0,.6.47,1.08,1.07,1.08h3.91v-3Z"/>
+</svg>
\ No newline at end of file
diff --git a/features/more-paint-functions/icons/subtract.svg b/features/more-paint-functions/icons/subtract.svg
new file mode 100644
index 00000000..4d10c195
--- /dev/null
+++ b/features/more-paint-functions/icons/subtract.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 13">
+  <defs>
+    <style>
+      .cls-1 {
+        opacity: .5;
+      }
+
+      .cls-1, .cls-2 {
+        fill: #7860aa;
+        fill-rule: evenodd;
+      }
+    </style>
+  </defs>
+  <path class="cls-2" d="M13,1.08v6.85c0,.6-.47,1.07-1.07,1.07h-2.94v-3.92c0-.59-.48-1.08-1.07-1.08h-3.91V1.07c0-.6.48-1.07,1.07-1.07h6.85c.6,0,1.07.49,1.07,1.08Z"/>
+  <path class="cls-1" d="M7.92,4H1.07c-.6,0-1.07.47-1.07,1.07h0v6.85c0,.6.47,1.08,1.07,1.08h6.85c.6,0,1.07-.47,1.07-1.07h0v-6.85c0-.6-.47-1.08-1.07-1.08Z"/>
+</svg>
\ No newline at end of file
diff --git a/features/more-paint-functions/icons/unite.svg b/features/more-paint-functions/icons/unite.svg
new file mode 100644
index 00000000..dee97002
--- /dev/null
+++ b/features/more-paint-functions/icons/unite.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 13">
+  <defs>
+    <style>
+      .cls-1 {
+        fill: #7860aa;
+        fill-rule: evenodd;
+      }
+    </style>
+  </defs>
+  <path class="cls-1" d="M11.93,0h-6.85c-.6,0-1.07.47-1.07,1.07h0v6.85c0,.6.47,1.08,1.07,1.08h6.85c.6,0,1.07-.47,1.07-1.07h0V1.08c0-.6-.47-1.08-1.07-1.08Z"/>
+  <path class="cls-1" d="M7.92,4H1.07c-.6,0-1.07.47-1.07,1.07h0v6.85c0,.6.47,1.08,1.07,1.08h6.85c.6,0,1.07-.47,1.07-1.07h0v-6.85c0-.6-.47-1.08-1.07-1.08Z"/>
+</svg>
\ No newline at end of file
diff --git a/features/more-paint-functions/script.js b/features/more-paint-functions/script.js
new file mode 100644
index 00000000..ddfd0849
--- /dev/null
+++ b/features/more-paint-functions/script.js
@@ -0,0 +1,161 @@
+export default async function ({ feature, console }) {
+  function unite() {
+    let paper = feature.traps.getPaper();
+    let items = paper.project.selectedItems;
+
+    if (items.length !== 2) return;
+
+    for (var i in items) {
+      if (i > 0) {
+        items[0].unite(items[i]);
+      }
+    }
+
+    for (var i in items) {
+      items[i].remove();
+    }
+
+    paper.tool.onUpdateImage();
+  }
+
+  function subtract() {
+    let paper = feature.traps.getPaper();
+    let items = paper.project.selectedItems;
+
+    if (items.length !== 2) return;
+
+    for (var i in items) {
+      if (i > 0) {
+        items[0].subtract(items[i]);
+      }
+    }
+
+    for (var i in items) {
+      items[i].remove();
+    }
+
+    paper.tool.onUpdateImage();
+  }
+
+  function exclude() {
+    let paper = feature.traps.getPaper();
+    let items = paper.project.selectedItems;
+
+    if (items.length !== 2) return;
+
+    for (var i in items) {
+      if (i > 0) {
+        items[0].exclude(items[i]);
+      }
+    }
+
+    for (var i in items) {
+      items[i].remove();
+    }
+
+    paper.tool.onUpdateImage();
+  }
+
+  function intersect() {
+    let paper = feature.traps.getPaper();
+    let items = paper.project.selectedItems;
+
+    if (items.length !== 2) return;
+
+    for (var i in items) {
+      if (i > 0) {
+        items[0].intersect(items[i]);
+      }
+    }
+
+    for (var i in items) {
+      items[i].remove();
+    }
+
+    paper.tool.onUpdateImage();
+  }
+
+  ScratchTools.waitForElements(
+    "div[class^='mode-tools_mod-labeled-icon-height_']",
+    async function (row) {
+      if (row.querySelector(".ste-more-functions")) return;
+
+      let functions = [
+        {
+          name: "Unite",
+          icon: "function-unite",
+          callback: unite,
+        },
+        {
+          name: "Subtract",
+          icon: "function-subtract",
+          callback: subtract,
+        },
+        {
+          name: "Exclude",
+          icon: "function-exclude",
+          callback: exclude,
+        },
+        {
+          name: "Intersect",
+          icon: "function-intersect",
+          callback: intersect,
+        },
+      ];
+
+      for (var i in functions) {
+        row.appendChild(makeButton(functions[i]));
+      }
+
+      let align = await ScratchTools.waitForElement(".ste-align-items");
+      row.appendChild(align);
+    }
+  );
+
+  feature.redux.subscribe(function () {
+    if (document.querySelector(".ste-more-functions")) {
+      let span = document.querySelector(".ste-more-functions");
+      if (
+        feature.traps.paint().format === "BITMAP" ||
+        feature.traps.paint().selectedItems?.length < 2
+      ) {
+        document.querySelectorAll(".ste-more-functions").forEach(function (el) {
+          el.classList.add("button_mod-disabled_1rf31");
+        });
+      } else {
+        document.querySelectorAll(".ste-more-functions").forEach(function (el) {
+          el.classList.remove("button_mod-disabled_1rf31");
+        });
+      }
+    }
+  });
+
+  function makeButton({ name, icon, callback }) {
+    let span = document.createElement("span");
+    span.className =
+      "button_button_u6SE2 labeled-icon-button_mod-edit-field_1bXYC ste-more-functions";
+    span.role = "button";
+
+    let img = document.createElement("img");
+    img.src = feature.self.getResource(icon);
+    img.className = "labeled-icon-button_edit-field-icon_3j-Pf";
+    img.alt = name;
+    img.title = name;
+    img.draggable = false;
+    span.appendChild(img);
+
+    let label = document.createElement("span");
+    label.textContent = name;
+    label.className = "labeled-icon-button_edit-field-title_1ZoEV";
+    span.appendChild(label);
+
+    span.addEventListener("click", function (e) {
+      if (span.className.includes("disabled")) return;
+      callback();
+    });
+
+    feature.self.hideOnDisable(span);
+
+    return span;
+  }
+}
diff --git a/features/more-paint-functions/style.css b/features/more-paint-functions/style.css
new file mode 100644
index 00000000..752ac1b7
--- /dev/null
+++ b/features/more-paint-functions/style.css
@@ -0,0 +1,7 @@
+div[class*='mode-tools_mode-tools_'] > div[class*='mode-tools_mod-dashed-border_']:nth-child(1), div[class*='mode-tools_mode-tools_'] > div[class*='mode-tools_mod-dashed-border_']:nth-child(2) {
+    display: none;
+}
+
+div[class*='mode-tools_mode-tools_'] > div[class*='mode-tools_mod-labeled-icon-height_']:nth-child(3) {
+    margin-left: 0px !important;
+}
\ No newline at end of file