-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Re-implement cat block rendering in Blockly v12 terms #3426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: spork
Are you sure you want to change the base?
Conversation
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
chore(deps): update node.js to v20
…kout-6.x chore(deps): update actions/checkout action to v6
…p-java-5.x chore(deps): update actions/setup-java action to v5
There was a problem hiding this 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
ScratchBlocksThemeenum supportingCLASSICandCAT_BLOCKSthemes - Refactored renderer architecture to use overridable
makeReplacementTop_()andmakeBowlerHat()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.
| 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); | ||
| }); |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmmm...
There was a problem hiding this comment.
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.
Proposed Changes
developintosporkReason 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.