Crackle, modding framework for Snap!

Yes, I know there already is a post for Crackle, and it might seem weird me making one even though the CrackleSDK - Devlog topic already exists, but that’s under @crackleteam , that sadly makes me not be able to post a reply more than twice (which is kind of needed for new updates). Also, not trying to be rude here, @tethrarxitet hasn’t done much work on it, so I thought I’ll make this new post.

CrackleLogo

Crackle is a modding framework for Snap!, meant to remove the pain of managing several userscripts and more easily create mods. Its a browser extension that simply injects into Snap!, for modding purposes.

Feel free to install it, try to create a mod, suggest features, or maybe even contribute! Instructions to install/use on the GitHub page, and most of all; enjoy!

Also, @tethrarxitet –if you have it–would you mind sending the old SVG of the Crackle logo over so I may add those Snap!-like colors to it, and have a SVG version which will overall be better.

I feel like making a Split! extension for Crackle would be a good idea, but that would take quite an effort to do so.

i’ll try to do that (no garuntee i will succeed)

Just make the Split blocks; don’t even TRY making the Split UI!

could you point me to where the blocks are drawn for split?

also at first i thought you meant like crackle being able to work with split, which i think is a cool idea also

in blocks.js

you’ll also need to update SpriteMorph.prototype.isHighContrast and SyntaxElementMorph.prototype.fixLayout

Mod ideas: changing color values in variable monitors, better color picker, clipboard system, backpack system.

Yeah! I think I could work on that!

EDIT: here’s a proof-of-concept demo, using my “better flat design” hack that I used for Split:

Code
return {
  id: "better-flat-design",
  name: "Better Flat Design",
  description: "Makes flat design BETTER!",
  version: "1.0.1",
  author: "d016",
  depends: [],
  doMenu: false,
  main(api) {
    InspectorMorph.prototype.init_ = InspectorMorph.prototype.init;
    InspectorMorph.prototype.init = function (...args) {
      this.init_(...args);
      this.edge = 5;
    };
    DialogBoxMorph.prototype.render_ = DialogBoxMorph.prototype.render;
    DialogBoxMorph.prototype.render = function (ctx) {
      var gradient,
        w = this.width(),
        h = this.height(),
        th = Math.floor(fontHeight(this.titleFontSize) + this.titlePadding * 2),
        shift = this.corner / 2,
        x,
        y,
        isFlat = MorphicPreferences.isFlat && !this.is3D;

      // this.alpha = isFlat ? 0.9 : 1;

      // title bar
      if (isFlat) {
        ctx.fillStyle = this.titleBarColor.toString();
      } else {
        gradient = ctx.createLinearGradient(0, 0, 0, th);
        gradient.addColorStop(
          0,
          this.titleBarColor.lighter(this.contrast / 2).toString(),
        );
        gradient.addColorStop(
          1,
          this.titleBarColor.darker(this.contrast).toString(),
        );
        ctx.fillStyle = gradient;
      }
      ctx.beginPath();
      this.outlinePathTitle(ctx, this.corner); //isFlat ? 0 : this.corner);
      ctx.closePath();
      ctx.fill();

      // flat shape
      // body
      ctx.fillStyle = this.color.toString();
      ctx.beginPath();
      this.outlinePathBody(ctx, this.corner); //isFlat ? 0 : this.corner);
      ctx.closePath();
      ctx.fill();

      if (isFlat) {
        return;
      }

      // 3D-effect
      // bottom left corner
      gradient = ctx.createLinearGradient(0, h - this.corner, 0, h);
      gradient.addColorStop(0, this.color.toString());
      gradient.addColorStop(1, this.color.darker(this.contrast.toString()));

      ctx.lineWidth = this.corner;
      ctx.lineCap = "round";
      ctx.strokeStyle = gradient;

      ctx.beginPath();
      ctx.moveTo(this.corner, h - shift);
      ctx.lineTo(this.corner + 1, h - shift);
      ctx.stroke();

      // bottom edge
      gradient = ctx.createLinearGradient(0, h - this.corner, 0, h);
      gradient.addColorStop(0, this.color.toString());
      gradient.addColorStop(1, this.color.darker(this.contrast.toString()));

      ctx.lineWidth = this.corner;
      ctx.lineCap = "butt";
      ctx.strokeStyle = gradient;

      ctx.beginPath();
      ctx.moveTo(this.corner, h - shift);
      ctx.lineTo(w - this.corner, h - shift);
      ctx.stroke();

      // right body edge
      gradient = ctx.createLinearGradient(w - this.corner, 0, w, 0);
      gradient.addColorStop(0, this.color.toString());
      gradient.addColorStop(1, this.color.darker(this.contrast).toString());

      ctx.lineWidth = this.corner;
      ctx.lineCap = "butt";
      ctx.strokeStyle = gradient;

      ctx.beginPath();
      ctx.moveTo(w - shift, th);
      ctx.lineTo(w - shift, h - this.corner);
      ctx.stroke();

      // bottom right corner
      x = w - this.corner;
      y = h - this.corner;

      gradient = ctx.createRadialGradient(x, y, 0, x, y, this.corner);
      gradient.addColorStop(0, this.color.toString());
      gradient.addColorStop(1, this.color.darker(this.contrast.toString()));

      ctx.lineCap = "butt";

      ctx.strokeStyle = gradient;

      ctx.beginPath();
      ctx.arc(x, y, shift, radians(90), radians(0), true);
      ctx.stroke();

      // left body edge
      gradient = ctx.createLinearGradient(0, 0, this.corner, 0);
      gradient.addColorStop(0, this.color.lighter(this.contrast).toString());
      gradient.addColorStop(1, this.color.toString());

      ctx.lineCap = "butt";
      ctx.strokeStyle = gradient;

      ctx.beginPath();
      ctx.moveTo(shift, th);
      ctx.lineTo(shift, h - this.corner * 2);
      ctx.stroke();

      // left vertical bottom corner
      gradient = ctx.createLinearGradient(0, 0, this.corner, 0);
      gradient.addColorStop(0, this.color.lighter(this.contrast).toString());
      gradient.addColorStop(1, this.color.toString());

      ctx.lineCap = "round";
      ctx.strokeStyle = gradient;

      ctx.beginPath();
      ctx.moveTo(shift, h - this.corner * 2);
      ctx.lineTo(shift, h - this.corner - shift);
      ctx.stroke();
    };
    SliderMorph.prototype.init_ = SliderMorph.prototype.init;
    SliderMorph.prototype.init = function (...args) {
      this.init_(...args);
      this.button.alpha = MorphicPreferences.isFlat ? 0.7 : 1;
      this.button.color = MorphicPreferences.isFlat
        ? new Color(100, 100, 100)
        : new Color(200, 200, 200);
      this.button.highlightColor = new Color(210, 210, 255);
      this.button.pressColor = new Color(180, 180, 255);
      this.alpha = MorphicPreferences.isFlat ? 0.08 : 0.3;
    };
    MenuMorph.prototype.createItems_ = MenuMorph.prototype.createItems;
    MenuMorph.prototype.createItems = function () {
      this.createItems_();
      this.edge = 5;
    };
    PushButtonMorph.prototype.drawBackground_ =
      PushButtonMorph.prototype.drawBackground;
    PushButtonMorph.prototype.drawBackground = function (ctx, color) {
      var isFlat = MorphicPreferences.isFlat && !this.is3D;

      ctx.fillStyle = color.toString();
      ctx.beginPath();
      this.outlinePath(
        ctx,
        Math.max(this.corner - this.outline, 0),
        this.outline,
      );
      ctx.closePath();
      ctx.fill();
      ctx.lineWidth = this.outline;
    };
    PushButtonMorph.prototype.drawOutline_ =
      PushButtonMorph.prototype.drawOutline;
    PushButtonMorph.prototype.drawOutline = function (ctx) {
      var outlineStyle,
        isFlat = MorphicPreferences.isFlat && !this.is3D,
        isTransparent = this.color.a < 1;

      if (!this.outline) {
        return null;
      }
      if (false) {
        //this.outlineGradient && !(!this.outline || isFlat)) {
        outlineStyle = ctx.createLinearGradient(0, 0, 0, this.height());
        outlineStyle.addColorStop(0, this.outlineColor.darker().toString());
        outlineStyle.addColorStop(1, "white");
      } else {
        outlineStyle = this.outlineColor.toString();
      }
      ctx.fillStyle = outlineStyle;
      ctx.strokeStyle = outlineStyle;
      ctx.lineWidth = this.outline;
      ctx.beginPath();
      this.outlinePath(ctx, this.corner, isTransparent ? this.outline / 2 : 0);
      ctx.closePath();
      isTransparent ? ctx.stroke() : ctx.fill();
    };
    BooleanSlotMorph.prototype.drawDiamond_ = BooleanSlotMorph.prototype.drawDiamond;
BooleanSlotMorph.prototype.drawDiamond = function (ctx, progress) {
    var w = this.width(),
        h = this.height(),
        r = h / 2,
        w2 = w / 2,
        edge = MorphicPreferences.isFlat ? this.flatEdge : this.edge,
        shift = edge / 2,
        gradient;

    // draw the 'flat' shape:
    if (this.cachedNormalColor) { // if flashing
        ctx.fillStyle = this.color.toString();
    } else if (progress < 0 ) { // 'fade'
        ctx.fillStyle = this.color.darker(25).toString();
    } else {
        switch (this.value) {
        case true:
            ctx.fillStyle = 'rgb(0, 200, 0)';
            break;
        case false:
            ctx.fillStyle = 'rgb(200, 0, 0)';
            break;
        default:
            ctx.fillStyle = this.color.darker(25).toString();
        }
    }

    if (progress > 0 && !this.isEmptySlot()) {
        // left half:
        ctx.fillStyle = 'rgb(0, 200, 0)';
        ctx.beginPath();
        ctx.moveTo(0, r);
        ctx.lineTo(r, 0);
        ctx.lineTo(w2, 0);
        ctx.lineTo(w2, h);
        ctx.lineTo(r, h);
        ctx.closePath();
        ctx.fill();

        // right half:
        ctx.fillStyle = 'rgb(200, 0, 0)';
        ctx.beginPath();
        ctx.moveTo(w2, 0);
        ctx.lineTo(w - r, 0);
        ctx.lineTo(w, r);
        ctx.lineTo(w - r, h);
        ctx.lineTo(w2, h);
        ctx.closePath();
        ctx.fill();
    } else {
        ctx.beginPath();
        ctx.moveTo(0, r);
        ctx.lineTo(r, 0);
        ctx.lineTo(w - r, 0);
        ctx.lineTo(w, r);
        ctx.lineTo(w - r, h);
        ctx.lineTo(r, h);
        ctx.closePath();
        ctx.fill();
    }

    // if (MorphicPreferences.isFlat) {return; }

    // add 3D-Effect:
    ctx.lineWidth = edge;
    ctx.lineJoin = 'round';
    ctx.lineCap = 'round';

    if (useBlurredShadows && !MorphicPreferences.isFlat) {
        ctx.shadowOffsetX = shift;
        ctx.shadowBlur = shift;
        ctx.shadowColor = 'black';
    }

    // top edge: left corner
    if (MorphicPreferences.isFlat) {
        gradient = (this.color).darker(this.contrast);
    } else {
        gradient = ctx.createLinearGradient(
            0,
            r,
            this.edge * 0.6,
            r + (this.edge * 0.6)
        );
        gradient.addColorStop(1, this.cachedClrDark);
        gradient.addColorStop(0, this.cachedClr);

    }
    ctx.strokeStyle = gradient;
    
    ctx.beginPath();
    ctx.moveTo(shift, r);
    ctx.lineTo(r, shift);
    ctx.closePath();
    ctx.stroke();

    // top edge: left bottom corner
    if (MorphicPreferences.isFlat) {
        ctx.beginPath();
    ctx.moveTo(shift, h - r);
    ctx.lineTo(r, h - shift);
    ctx.closePath();
    ctx.stroke();
    }

    // top edge: straight line
    if (useBlurredShadows && !MorphicPreferences.isFlat) {
        ctx.shadowOffsetX = 0;
        ctx.shadowOffsetY = shift;
        ctx.shadowBlur = this.edge;
    }
    if (MorphicPreferences.isFlat) {
        gradient = (this.color).darker(this.contrast);
    } else {
    gradient = ctx.createLinearGradient(
        0,
        0,
        0,
        this.edge
    );
    gradient.addColorStop(1, this.cachedClrDark);
    gradient.addColorStop(0, this.cachedClr);
}
    ctx.strokeStyle = gradient;
    ctx.beginPath();
    ctx.moveTo(r, shift);
    ctx.lineTo(w - r, shift);
    ctx.closePath();
    ctx.stroke();

    ctx.shadowOffsetY = 0;
    ctx.shadowBlur = 0;

    // bottom edge: right corner
    if (MorphicPreferences.isFlat) {
        gradient = (this.color).darker(this.contrast);
    } else {
    gradient = ctx.createLinearGradient(
        w - r - (this.edge * 0.6),
        h - (this.edge * 0.6),
        w - r,
        h
    );
    gradient.addColorStop(1, this.cachedClr);
    gradient.addColorStop(0, this.cachedClrBright);
}
    ctx.strokeStyle = gradient;
    ctx.beginPath();
    ctx.moveTo(w - r, h - shift);
    ctx.lineTo(w - shift, r);
    ctx.closePath();
    ctx.stroke();

    // top edge: right corner
    if (MorphicPreferences.isFlat) {
        ctx.beginPath();
    ctx.moveTo(w - r, shift);
    ctx.lineTo(w - shift, h - r);
    ctx.closePath();
    ctx.stroke();
    }

    // bottom edge: straight line
    if (MorphicPreferences.isFlat) {
        gradient = (this.color).darker(this.contrast);
    } else {
    gradient = ctx.createLinearGradient(
        0,
        h - this.edge,
        0,
        h
    );
    gradient.addColorStop(1, this.cachedClr);
    gradient.addColorStop(0, this.cachedClrBright);
}
    ctx.strokeStyle = gradient;
    ctx.beginPath();
    ctx.moveTo(r, h - shift);
    ctx.lineTo(w - r - shift, h - shift);
    ctx.closePath();
    ctx.stroke();
};
InputSlotMorph.prototype.render_ = InputSlotMorph.prototype.render;
InputSlotMorph.prototype.render = function (ctx) {
    var borderColor, r, fillStyle, edge;

    // initialize my surface property
    if (this.cachedNormalColor) { // if flashing
        borderColor = this.color;
    } else if (this.parent) {
        borderColor = this.parent.color;
    } else {
        borderColor = new Color(120, 120, 120);
    }
    fillStyle = this.color.toString();
    if (this.isReadOnly && !this.cachedNormalColor) { // unless flashing
        fillStyle = borderColor.darker().toString();
    }
    ctx.fillStyle = fillStyle;

    // cache my border colors
    this.cachedClr = borderColor.toString();
    this.cachedClrBright = borderColor.lighter(this.contrast)
        .toString();
    this.cachedClrDark = borderColor.darker(this.contrast).toString();
    edge = MorphicPreferences.isFlat ? this.flatEdge : this.edge
    if (!this.isNumeric) {
        if (MorphicPreferences.isFlat) {
            ctx.fillStyle = borderColor.darker(this.contrast).toString();
            ctx.fillRect(
            0,
            0,
            this.width(),
            this.height()
        );
        ctx.fillStyle = fillStyle;
        }
        ctx.fillRect(
            edge,
            edge,
            this.width() - edge * 2,
            this.height() - edge * 2
        );
        if (!MorphicPreferences.isFlat) {
            this.drawRectBorder(ctx);
        }
    } else {
        var drawRoundSlot = (e) => {
            r = Math.max((this.height() - (e * 2)) / 2, 0);
            ctx.beginPath();
            ctx.arc(
                r + e,
                r + e,
                r,
                radians(90),
                radians(-90),
                false
            );
            ctx.arc(
                this.width() - r - e,
                r + e,
                r,
                radians(-90),
                radians(90),
                false
            );
            ctx.closePath();
            ctx.fill();
        };
        if (MorphicPreferences.isFlat) {
            ctx.fillStyle = borderColor.darker(this.contrast).toString();
            drawRoundSlot(0);
            ctx.fillStyle = fillStyle;
        };

        drawRoundSlot(edge);

        if (!MorphicPreferences.isFlat) {
            this.drawRoundBorder(ctx);
        }
    }

	// draw my "wish" block, if any
	if (this.selectedBlock) {
 		ctx.drawImage(
        	this.doWithAlpha(1, () => this.selectedBlock.fullImage()),
            this.edge + this.typeInPadding,
            this.edge
        );
 	}
};
    MorphicPreferences.isFlat = true;
    api.ide.refreshIDE();
  },
  cleanupFuncs: [
    function () {
      DialogBoxMorph.prototype.render = DialogBoxMorph.prototype.render_;
      MenuMorph.prototype.createItems = MenuMorph.prototype.createItems_;
      SliderMorph.prototype.init = SliderMorph.prototype.init_;
      PushButtonMorph.prototype.drawBackground =
        PushButtonMorph.prototype.drawBackground_;
      PushButtonMorph.prototype.drawOutline =
        PushButtonMorph.prototype.drawOutline_;
      InspectorMorph.prototype.init = InspectorMorph.prototype.init_;
      InputSlotMorph.prototype.render = InputSlotMorph.prototype.render_;
      BooleanSlotMorph.prototype.drawDiamond = BooleanSlotMorph.prototype.drawDiamond_;
    },
  ],
};

By the way, the userscript removes the Crackle button for some reason. My guess is that the button was added hastily

Because I created the button directly instead of modifying the createControlBar function.. let me change it up in Crackle.

Here’s the PR for create the Crackle button during createControlBar:

image

Merged!

By the way, could you change the README.md to include this?

# Loading in browser
For now, CrackleSDK does not have any pages for it on common browser extension stores. On, Firefox go to `about:debugging`, go to `This Firefox`, click `Load Temporary Add-on...` and select the `manifest.json` file in this directory. Now, whenever you launch Snap! you should see the new addon button. On chrome, go to the "Manage Extensions" option in the extension menu [chrome://extensions/](chrome://extensions/). At the top right corner, there's slider with a "Developer Mode" label. After turning on that slider, there should be a "Load unpacked" button at the top left. Import your CrackleSDK clone in there, and see the results.

For chrome, you have to make sure to have developer mode on to see the “load unpacked” button.

And just a note for mod developers (saw two pull requests for mods that did this).
CrackleSDK is more geared for mods to the Snap! interface, not anything that would make projects incompatible with normal Snap! without any mods.

By the way, thank you for adding me as a collaborator:

Just make sure to create a release soon!

That was on your fork. I don’t have power to add you, as @tethrarxitet is the owner of CrackleTeam. I can still merge PRs though, so that’s not a issue.

But anyway, I will probably add a release. A pre-release that is, because I still don’t feel like its in a good state (I don’t have a online mod downloader screen!)

Oh, right. I forgot. Thanks!

I’m not sure how that would work. Where will the online mods come from? The github repository?

Yes, CrackleTeam/CrackleMods

You should probably call them addons instead of mods