New "Tutorials" library - how to use?

Recently, I was testing the dev version of Snap!, and I couldn’t help but notice the new extensions scn and meta.

Browsing further, I noticed a new library named a ToggleButtonMorph 1 [32@133 | 174@17]

And in this category are those extensions I found. However, when running each block and extension, nothing seemed to work. Well, I found a new setting - a MenuItemMorph 1 [224@259 | 145@14]

Checked the box, tried running a block, and it did nothing - it only slowed down the editor, like warping a forever loop.

So what’s the deal with this? I can tell that it’s in development, but I have no idea how to use it correctly. Hopefully, when it’s fully released, there’s an in-depth explanation on how to work with it. Thanks!

Because you can’t. As far as I’ve been able to tell, it has no functionality, because it’s incomplete.

I just said this in another thread, but I think it fits here:

My advice is to wait for the feature to actually be made before asking about it, because I feel like it could be a cool one

After you mark a scene as a tutorial, you have to either right click the scene (when you’re not in it) and click “launch”, or click file > launch tutorial to actually launch the scene as a tutorial. The blocks are not meant to be ran outside a tutorial, so they do nothing when you just click them.

I know now… :slight_smile: I was trying to make a tutorial on how to draw a costume and make it look good.

That’s exactly right, thank you for the great explanation, @ego-lay_atman-bay .

We’re working on a set of features to use Snap! for a new middle school curriculum here in Germany (Baden-Württemberg) next year, but those features will also be used elsewhere. At first I believed that those would be just a bunch of minor quick tweaks, but they sorta grew to become a rather major release, and it just might take us some more months to complete them, perhaps even as long as next Spring or Summer, and maybe they’ll end up becoming v12 rather than v11.1 :slight_smile:

  • Templates: Marking a Project/Scene as “template” removes its name when opening it again, it becomes “unnamed” and when you want to save it Snap! prompts you for a new name. This lets you share starter projects / puzzles & microworlds that your audience can better individualize. This feature is basically completed.
  • Tutorials: Marking a Scene as “tutorial” lets you “launch” the scene in a separate floating, resizable dialog box, which is fully interactive dialog, from which you can decide to let the user drag out resources such as blocks etc. That tutorial scene can also act as an autograder because it has access to the contents of the IDE scene, so it can compare scripts etc. and give hints using metaprogramming. This feature is still under construction, it’s basically at a state at which we are already heavily using it for the curriculum we’re working on, so we find out which other things it needs.
  • When you have a project whose initial scene is a template and that also has a tutorial scene, the tutorial scene gets launched automatically when opening the template. Saving the template leaves out the tutorial, so you can use that as a starter project without cluttering up each individual spin-off with an embedded tutorial.
  • Seamless Magnification: To better serve learners with visual impairments (including old folks like moi, haha) you can seamlessly and smoothly magnify everything (not just blocks) without having to serialize and deserialize the project. This is also great for presenting before an audience. This is done, what’s left is adding options to the API so web sites and maybe also templates / microworlds can open the editor with a magnification / stage scale parameter.
  • Drawing / stamping & writing on sprites: In addition to being able to draw, stamp and write on the global (stage’s) pen trails layer you can also let a sprite draw, stamp and write on any other sprite, which is great fun, especially if the other sprite is moving, rotating etc. We’ve got the geometry and functionality all working but are still figuring out the user interface for this.
  • AI image recognition: We’re also working on a complete new extension that lets you track facial and body features for fun interaction with other sprites. This is mostly done but not published in the repo/distro yet. You’ll really love it, it’s much better than… ah, you’ll see for yourselves :slight_smile:
  • This is an incomplete list, there’s more in the pipeline, but you can already check out most of these things in the beta channel.
  • Oh, I almost forgot: Yes, the tab buttons get new little icons for “scripts, costumes and sounds”, so we can use Snap! with kids who can’t yet read. This is done.

Ooh, Scratch just added something similar (after being in the Scratch lab pipeline for several years lol).

I saw @owlsss’s screenshot of the dev version from another thread and saw those icons in it. I did a double take and wondered whether they were new or I was just inobservant. Turns out both :slight_smile: .

Thank you! I have been making a drawing project for a few months now and have come up with a good layer system that I haven’t seen anywhere else. It’s just, I can’t do make multiple layers for a multitude of reasons, which this ability should hopefully fix. In addition, said project has a fill tool which “works”, but makes the layer really blurry because of how layering is implemented.

PS: Could you also add an erase mode? I’m not entirely sure how you would go about this, but perhaps before a sprite switches to costume that isn’t one of the ones in its own costumes (such as being stamped on), retain the costume it was last using that is one of its costumes in memory and use that to know what pixels to place when erasing pen trails?

Oh that’s pretty cool! I know many regular snap users use a set of custom blocks in every project they make, so making an official template system can help with them too (and not have to worry about people overriding their template project).

This is also pretty cool! I remember creating a library to be able to draw over sprites by creating a line costume and pasting it on the target sprite (and also overriding the set movement blocks to make it seamless), though it was limited to using flat line ends and I didn’t add writing text.

I noticed you mentioned stamping a few times, is that different from the paste on block?

Nice, I’m assuming it’ll be better than scratch, and allow you to do more things with it.

I noticed, and they look pretty cool (though this does mean 2 new icons I have to recreate for snapblocks).

Sorry about that, but these are actually referred as to “symbols”.

These are like any other symbol, and thus can be used in block labels, so yes.

Those “symbols” are being used as icons.

You’re telling this to the main developer of Snap; I think he knows the technical name for them considering he was the one who gave them such a name. Also, they are being used as icons.

I recommend that “Template” and “Tutorial” be moved/added to the Stage script area, like this:

That way, it fills the gap that the “draggable” button left there.

I rewrote the function that adds the draggable button:

IDE_Morph.prototype.createSpriteBar = function () {
    // assumes that the categories pane has already been created
    var rotationStyleButtons = [],
        thumbSize = new Point(45, 45),
        nameField,
        padlock,
        thumbnail,
        tabCorner = 15,
        tabColors = this.tabColors,
        tabBar = new AlignmentMorph('row', -tabCorner * 2),
        tab,
        symbols = [
            new SymbolMorph('arrowRightThin', 10),
            new SymbolMorph('turnAround', 10),
            new SymbolMorph('arrowLeftRightThin', 10),
        ],
        labels = ["don't rotate", 'can rotate', 'only face left/right'],
        myself = this;

    if (this.spriteBar) {
        this.spriteBar.destroy();
    }

    this.spriteBar = new Morph();
    this.spriteBar.color = this.frameColor;
    this.add(this.spriteBar);

    function addRotationStyleButton(rotationStyle) {
        var colors = myself.rotationStyleColors,
            button;

        button = new ToggleButtonMorph(
            colors,
            myself, // the IDE is the target
            () => {
                if (myself.currentSprite instanceof SpriteMorph) {
                    myself.currentSprite.rotationStyle = rotationStyle;
                    myself.currentSprite.changed();
                    myself.currentSprite.fixLayout();
                    myself.currentSprite.rerender();
                    myself.currentSprite.recordUserEdit(
                        'sprite',
                        'rotation',
                        rotationStyle
                    );
                }
                rotationStyleButtons.forEach(each =>
                    each.refresh()
                );
            },
            symbols[rotationStyle], // label
            () => myself.currentSprite instanceof SpriteMorph // query
                && myself.currentSprite.rotationStyle === rotationStyle,
            null, // environment
            localize(labels[rotationStyle])
        );

        button.corner = 8;
        button.labelMinExtent = new Point(11, 11);
        button.padding = 0;
        button.labelShadowOffset = new Point(-1, -1);
        button.labelShadowColor = colors[1];
        button.labelColor = myself.buttonLabelColor;
        button.fixLayout();
        button.refresh();
        rotationStyleButtons.push(button);
        button.setPosition(myself.spriteBar.position().add(new Point(2, 4)));
        button.setTop(button.top()
            + ((rotationStyleButtons.length - 1) * (button.height() + 2))
            );
        myself.spriteBar.add(button);
        if (myself.currentSprite instanceof StageMorph) {
            button.hide();
        }
        return button;
    }

    addRotationStyleButton(1);
    addRotationStyleButton(2);
    addRotationStyleButton(0);
    this.rotationStyleButtons = rotationStyleButtons;

    thumbnail = new Morph();
    thumbnail.isCachingImage = true;
    thumbnail.bounds.setExtent(thumbSize);
    thumbnail.cachedImage = this.currentSprite.thumbnail(thumbSize);
    thumbnail.setPosition(
        rotationStyleButtons[0].topRight().add(new Point(5, 3))
    );
    this.spriteBar.add(thumbnail);

    thumbnail.fps = 3;

    thumbnail.step = function () {
        if (thumbnail.version !== myself.currentSprite.version) {
            thumbnail.cachedImage = myself.currentSprite.thumbnail(
                thumbSize,
                thumbnail.cachedImage
            );
            thumbnail.changed();
            thumbnail.version = myself.currentSprite.version;
        }
    };

    nameField = new InputFieldMorph(this.currentSprite.name);
    nameField.setWidth(100); // fixed dimensions
    nameField.contrast = 90;
    nameField.setPosition(thumbnail.topRight().add(new Point(10, 3)));
    this.spriteBar.add(nameField);
    this.spriteBar.nameField = nameField;
    nameField.fixLayout();
    nameField.accept = function () {
        var newName = nameField.getValue();
        myself.currentSprite.setName(
            myself.newSpriteName(newName, myself.currentSprite)
        );
        nameField.setContents(myself.currentSprite.name);
    };
    this.spriteBar.reactToEdit = nameField.accept;

    createControlToggle = (label, callback, getter) => {
        let controlToggle = new ToggleMorph(
            'checkbox',
            null,
            callback,
            label,
            getter
        );

        controlToggle.label.isBold = false;
        controlToggle.label.setColor(this.buttonLabelColor);
        controlToggle.color = tabColors[2];
        controlToggle.highlightColor = tabColors[0];
        controlToggle.pressColor = tabColors[1];

        controlToggle.tick.shadowOffset = MorphicPreferences.isFlat ?
            ZERO : new Point(-1, -1);
        controlToggle.tick.shadowColor = BLACK;
        controlToggle.tick.color = this.buttonLabelColor;
        controlToggle.tick.isBold = false;
        controlToggle.tick.fixLayout();

        controlToggle.setPosition(nameField.bottomLeft().add(2));
        controlToggle.fixLayout();
    
        return controlToggle;
    }

    // padlock
    padlock = createControlToggle(
        localize('draggable'),
        () => {
            this.currentSprite.isDraggable = !this.currentSprite.isDraggable;
            this.currentSprite.recordUserEdit(
                'sprite',
                'draggable',
                this.currentSprite.isDraggable
            );
        },
        () => this.currentSprite.isDraggable), 
    this.spriteBar.add(padlock);

    sceneControls = new AlignmentMorph('row');
    template = createControlToggle(
        localize('template'),
        () => {
            this.scene.role = this.scene.role === 'template' ?
            null : 'template'
        },
        () => this.scene.role === 'template'
    );
    tutorial = createControlToggle(
        localize('tutorial'),
        () => {
            this.scene.role = this.scene.role === 'tutorial' ?
            null : 'tutorial'
        },
        () => this.scene.role === 'tutorial'
    );

    sceneControls.add(template);
    sceneControls.add(tutorial);
    sceneControls.fixLayout();

    this.spriteBar.add(sceneControls);

    if (this.currentSprite instanceof StageMorph) {
        padlock.hide();
    } else {
        sceneControls.hide();
    }

    // tab bar
    tabBar.tabTo = function (tabString) {
        var active;
        if (myself.currentTab === tabString) {return; }
        myself.world().hand.destroyTemporaries();
        myself.currentTab = tabString;
        this.children.forEach(each => {
            each.refresh();
            if (each.state) {active = each; }
        });
        active.refresh(); // needed when programmatically tabbing
        myself.createSpriteEditor();
        myself.fixLayout('tabEditor');
    };

    tab = new TabMorph(
        tabColors,
        null, // target
        () => tabBar.tabTo('scripts'),
        [ // label
            new SymbolMorph('blocks', 10),
            localize('Scripts')
        ],
        () => this.currentTab === 'scripts' // query
    );
    tab.padding = 3;
    tab.corner = tabCorner;
    tab.edge = 1;
    tab.labelShadowOffset = new Point(-1, -1);
    tab.labelShadowColor = tabColors[1];
    tab.labelColor = this.buttonLabelColor;

    tab.getPressRenderColor = function () {
        if (MorphicPreferences.isFlat ||
                SyntaxElementMorph.prototype.alpha > 0.85) {
            return this.pressColor;
        }
        return this.pressColor.mixed(
            Math.max(SyntaxElementMorph.prototype.alpha - 0.15, 0),
            SpriteMorph.prototype.paletteColor
        );
    };

    tab.fixLayout();
    tabBar.add(tab);

    tab = new TabMorph(
        tabColors,
        null, // target
        () => tabBar.tabTo('costumes'),
        [ // label
            new SymbolMorph('brush', 10),
            localize(this.currentSprite instanceof SpriteMorph ? 'Costumes'
                : 'Backgrounds')
        ],
        () => this.currentTab === 'costumes' // query
    );
    tab.padding = 3;
    tab.corner = tabCorner;
    tab.edge = 1;
    tab.labelShadowOffset = new Point(-1, -1);
    tab.labelShadowColor = tabColors[1];
    tab.labelColor = this.buttonLabelColor;
    tab.fixLayout();
    tabBar.add(tab);

    tab = new TabMorph(
        tabColors,
        null, // target
        () => tabBar.tabTo('sounds'),
        [ // label
            new SymbolMorph('speaker', 10),
            localize('Sounds')
        ],
        () => this.currentTab === 'sounds' // query
    );
    tab.padding = 3;
    tab.corner = tabCorner;
    tab.edge = 1;
    tab.labelShadowOffset = new Point(-1, -1);
    tab.labelShadowColor = tabColors[1];
    tab.labelColor = this.buttonLabelColor;
    tab.fixLayout();
    tabBar.add(tab);

    tabBar.fixLayout();
    tabBar.children.forEach(each =>
        each.refresh()
    );
    this.spriteBar.tabBar = tabBar;
    this.spriteBar.add(this.spriteBar.tabBar);

    this.spriteBar.fixLayout = function () {
        this.tabBar.setLeft(this.left());
        this.tabBar.setBottom(this.bottom() + myself.padding);
    };
};

So glad the sprite bar is already prepared for AlignmentMorphs. :slight_smile:

are we going to have any 11.x.0 updates from now to v12 (like the AI detection thing) if tutorials is v12?

yeah, this kinda makes sense but it also is kinda not discoverable.

The stage is the predominant part of a template?

It’s the scene that’s a part of a template / tutorial. I chose to add the settings in the Stage because, again, of that empty space that’s been there forever.

Old version:

Contender: