Skip to content

Conversation

@cwillisf
Copy link
Contributor

Proposed Changes

  • Merge latest changes from develop into spork
  • Re-implement cat block rendering in Blockly v12 terms

Reason for Changes

  • ^•⩊•^

@gonfunko, I'm very interested in any refactorings you think might make future Blockly upgrades smoother, or if there's a better or more official mechanism for any of this. I'm not thrilled with CatBlockSvg, for example.

KManolov3 and others added 16 commits November 4, 2025 17:07
feat: support cat-blocks as a configurable theme
# [1.3.0](v1.2.5...v1.3.0) (2025-11-26)

### Bug Fixes

* some silly issues and a rename ([acfb3e6](acfb3e6))
* use getters to dynamically update svg block constants ([4bd9618](4bd9618))

### Features

* abstract away some of the cat-blocks implementation logic ([c44cb57](c44cb57))
* add comment ([c88ca72](c88ca72))
* make cat-blocks code configurable behind a flag ([45a93bb](45a93bb))
…p-node-6.x

chore(deps): update actions/setup-node action to v6
…kout-6.x

chore(deps): update actions/checkout action to v6
…p-java-5.x

chore(deps): update actions/setup-java action to v5
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR re-implements the cat block rendering feature to work with Blockly v12 APIs, refactoring the renderer architecture to support multiple rendering themes. The changes merge the latest from develop and introduce a theme-based system for selecting between classic Scratch blocks and animated cat blocks.

Key Changes

  • Introduced a theme-based renderer selection system with ScratchBlocksTheme enum supporting CLASSIC and CAT_BLOCKS themes
  • Refactored renderer architecture to use overridable makeReplacementTop_() and makeBowlerHat() methods for better extensibility
  • Implemented cat block renderer with interactive animations including blinking eyes and flickable ears

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/renderer/renderer.ts Added getClassName() method and renamed renderer registration from "scratch" to "scratch_classic"
src/renderer/render_info.ts Extracted bowler hat creation into overridable makeBowlerHat() method for subclass customization
src/renderer/drawer.ts Refactored hat path replacement logic into makeReplacementTop_() method to support subclass overrides
src/renderer/constants.ts Added BOWLER_HAT_HEIGHT constant and makeBowlerHatPath() method; fixed unnecessary semicolon
src/renderer/bowler_hat.ts Updated to use BOWLER_HAT_HEIGHT from constants instead of hardcoded value
src/renderer/cat/renderer.ts New cat renderer that extends ScratchRenderer and registers as "scratch_catblocks"
src/renderer/cat/drawer.ts New drawer with face rendering and interactive animations for cat blocks
src/renderer/cat/constants.ts Cat-specific constants including face geometry paths and dynamic cat path generation
src/renderer/cat/cat_block_svg.ts Interface extending BlockSvg with hasFace flag to track face creation state
src/index.ts Added theme-based renderer selection with ScratchBlocksOptions interface
src/constants.ts Added ScratchBlocksTheme enum defining available block themes

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +80 to +138
setupBlinking_() {
const blinkDuration = 100;
let ignoreBlink = false;

// TODO: Would it be better to use CSS for this?
this.block_.pathObject.svgPath.addEventListener("mouseenter", () => {
if (ignoreBlink) return;
ignoreBlink = true;
setVisibility(this.parts_[FacePart.EYE_1_OPEN], false);
setVisibility(this.parts_[FacePart.EYE_2_OPEN], false);
setVisibility(this.parts_[FacePart.EYE_1_CLOSED], true);
setVisibility(this.parts_[FacePart.EYE_2_CLOSED], true);
setTimeout(() => {
setVisibility(this.parts_[FacePart.EYE_1_OPEN], true);
setVisibility(this.parts_[FacePart.EYE_2_OPEN], true);
setVisibility(this.parts_[FacePart.EYE_1_CLOSED], false);
setVisibility(this.parts_[FacePart.EYE_2_CLOSED], false);
}, blinkDuration);
setTimeout(() => {
ignoreBlink = false;
}, 2 * blinkDuration);
});
}

setupEarFlicks_() {
const flickDuration = 50;
let ignoreFlick1 = false;
let ignoreFlick2 = false;

this.parts_[FacePart.EAR_1_INSIDE].addEventListener("mouseenter", () => {
if (ignoreFlick1) return;
ignoreFlick1 = true;
setVisibility(this.parts_[FacePart.EAR_1_INSIDE], false);
this.pathEarState.ear1State = PathEarState.DOWN;
this.redraw();
setTimeout(() => {
setVisibility(this.parts_[FacePart.EAR_1_INSIDE], true);
this.pathEarState.ear1State = PathEarState.UP;
this.redraw();
}, flickDuration);
setTimeout(() => {
ignoreFlick1 = false;
}, 2 * flickDuration);
});
this.parts_[FacePart.EAR_2_INSIDE].addEventListener("mouseenter", () => {
if (ignoreFlick2) return;
ignoreFlick2 = true;
setVisibility(this.parts_[FacePart.EAR_2_INSIDE], false);
this.pathEarState.ear2State = PathEarState.DOWN;
this.redraw();
setTimeout(() => {
setVisibility(this.parts_[FacePart.EAR_2_INSIDE], true);
this.pathEarState.ear2State = PathEarState.UP;
this.redraw();
}, flickDuration);
setTimeout(() => {
ignoreFlick2 = false;
}, 2 * flickDuration);
});
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Event listeners added to DOM elements in setupBlinking_ and setupEarFlicks_ are never removed, which could lead to memory leaks if blocks are frequently created and destroyed. Consider storing references to the event listener functions and implementing cleanup logic, or verify that the Blockly framework handles this automatically when blocks are disposed.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmmm...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't currently see a good place to hook a dispose()-like or destroy()-like function to clean things up on a per-block basis. I'll continue poking around, but I could use a hint - even if it involves completely rearranging this code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants