new menu functionality for ASK command, when passing a list

snap8
Jens Mönig 2022-03-31 15:54:32 +02:00
rodzic ed15709f1e
commit 12a2739cc1
4 zmienionych plików z 467 dodań i 27 usunięć

Wyświetl plik

@ -2,6 +2,7 @@
## in development:
* **New Features:**
* passing a list to the ASK command in sensing presents a menu to the user
* export script (including dependencies) via its context menu
* export / import sprite-local custom block definitions from the palette
* added "combinations" primitive to the palette
@ -26,6 +27,9 @@
* **Translation Updates:**
* German
### 2022-03-31
* threads, objects: new menu functionality for ASK command, when passing a list
### 2022-03-28
* new "Tad" turtle costumes, thanks, Meghan and Brian!
* blocks, threads: new "position" choice in OF reporter's attribute dropdown, reports a list of XY coordinates

Wyświetl plik

@ -17,8 +17,8 @@
<script src="src/symbols.js?version=2021-03-03"></script>
<script src="src/widgets.js?version=2021-17-09"></script>
<script src="src/blocks.js?version=2022-03-28"></script>
<script src="src/threads.js?version=2022-03-28"></script>
<script src="src/objects.js?version=2022-03-16"></script>
<script src="src/threads.js?version=2022-03-31"></script>
<script src="src/objects.js?version=2022-03-31"></script>
<script src="src/scenes.js?version=2022-03-03"></script>
<script src="src/gui.js?version=2022-03-17"></script>
<script src="src/paint.js?version=2021-07-05"></script>

Wyświetl plik

@ -49,7 +49,13 @@
CellMorph
WatcherMorph
StagePrompterMorph
StagePickerMorph
StagePickerItemMorph
MenuItemMorph*
StagePickerItemMorph
MenuMorph*
StagePickerMorph
SpeechBubbleMorph*
SpriteBubbleMorph
@ -83,11 +89,11 @@ SpeechBubbleMorph, InputSlotMorph, isNil, FileReader, TableDialogMorph, String,
BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, BooleanSlotMorph,
localize, TableMorph, TableFrameMorph, normalizeCanvas, VectorPaintEditorMorph,
AlignmentMorph, Process, WorldMap, copyCanvas, useBlurredShadows,
BlockVisibilityDialogMorph, CostumeIconMorph, SoundIconMorph*/
BlockVisibilityDialogMorph, CostumeIconMorph, SoundIconMorph, MenuItemMorph*/
/*jshint esversion: 6*/
modules.objects = '2022-March-18';
modules.objects = '2022-March-31';
var SpriteMorph;
var StageMorph;
@ -103,6 +109,8 @@ var WatcherMorph;
var StagePrompterMorph;
var Note;
var SpriteHighlightMorph;
var StagePickerMorph;
var StagePickerItemMorph;
function isSnapObject(thing) {
return thing instanceof SpriteMorph || (thing instanceof StageMorph);
@ -8176,6 +8184,9 @@ StageMorph.prototype.setScale = function (number) {
}
morph.setCenter(this.center());
morph.setBottom(this.bottom());
} else if (morph instanceof StagePickerMorph) {
morph.createItems(number);
morph.popup(this, morph.position());
}
});
};
@ -12727,6 +12738,7 @@ StagePrompterMorph.prototype.init = function (question) {
// question is optional in case the Stage is asking
// additional properties
this.answer = null;
this.isDone = false;
if (question) {
this.label = new StringMorph(
@ -12800,5 +12812,399 @@ StagePrompterMorph.prototype.mouseClickLeft = function () {
};
StagePrompterMorph.prototype.accept = function () {
this.answer = this.inputField.getValue();
this.isDone = true;
};
// StagePickerMorph ////////////////////////////////////////////////////////
/*
I am a sensor-category-colored input box which lets the user pick one
from a list of options.
*/
// StagePickerMorph inherits from MenuMorph:
StagePickerMorph.prototype = new MenuMorph();
StagePickerMorph.prototype.constructor = StagePickerMorph;
StagePickerMorph.uber = MenuMorph.prototype;
// StagePickerMorph instance creation:
function StagePickerMorph(options) {
this.init(options);
}
StagePickerMorph.prototype.init = function (options) {
var first = options.at(1),
isSubmenu = this.isSubmenu(options),
title = isSubmenu ? first : null,
items = isSubmenu ? options.at(2) : options;
// additional properties
this.answer = null;
this.isDone = false;
this.scale = 1;
// initialize inherited properties
StagePickerMorph.uber.init.call(
this,
choice => {
var root = this.rootMenu();
root.answer = choice;
root.isDone = true;
},
title, // title
this, // environment
null // font size
);
// override inherited behavior
// create items
items.map(each => {
var key, value, isLine;
if (this.isSubmenu(each)) {
this.addMenu(
each.at(1), // label
new StagePickerMorph(each.at(2)), // aMenu
null, // indicator
true // verbatim, don't translate
);
} else {
key = each;
value = each;
if (each instanceof List) { // treat as pair
isLine = each.isEmpty();
key = each.at(1);
value = each.at(2);
}
if (isLine) {
this.addLine();
} else {
this.addItem(
this.dataRepresentation(key),
value,
null, // hint
null, // color
null, // bold
null, // italic
null, // doubleClickAction
null, // shortcut
true // verbatim? don't translate
);
}
}
});
};
StagePickerMorph.prototype.isSubmenu = function (options) {
var first;
if (!(options instanceof List)) {
return false;
}
first = options.at(1);
return isString(first) &&
first.length &&
options.length() === 2 &&
options.rank() > 1;
};
StagePickerMorph.prototype.dataRepresentation = function (data) {
switch (Process.prototype.reportTypeOf(data)) {
case 'number':
return data.toString();
default:
return data;
}
};
// StagePickerMorph popping up
StagePickerMorph.prototype.popup = function (stage, pos) {
var scroller;
this.setPosition(pos);
this.keepWithin(stage);
if (this.bottom() > stage.bottom()) {
// scroll menu items if the menu is taller than the stage
scroller = this.scroll();
this.bounds.corner.y = stage.bottom() - 2;
scroller.setHeight(stage.bottom() - scroller.top() - this.edge - 2);
scroller.adjustScrollBars();
}
if (this.items.length < 1 && !this.title) { // don't show empty menus
return;
}
stage.add(this);
this.fullChanged();
};
StagePickerMorph.prototype.createLabel = function () {
var text;
if (this.label !== null) {
this.label.destroy();
}
text = new TextMorph(
localize(this.title),
SpriteMorph.prototype.bubbleFontSize * this.scale,
null, // MorphicPreferences.menuFontName,
true,
false,
'center'
);
text.alignment = 'center';
text.color = WHITE;
text.backgroundColor = this.borderColor;
text.fixLayout();
this.label = new BoxMorph(this.edge, 0);
this.label.color = this.borderColor;
this.label.borderColor = this.borderColor;
this.label.outlinePath = function (ctx, corner, inset) {
// modify to only draw the top corners rounded
var w = this.width(),
h = this.height(),
radius = Math.min(corner, (Math.min(w, h) - inset) / 2),
offset = radius + inset;
// top left:
ctx.arc(
offset,
offset,
radius,
radians(-180),
radians(-90),
false
);
// top right:
ctx.arc(
w - offset,
offset,
radius,
radians(-90),
radians(-0),
false
);
// bottom right:
ctx.lineTo(w, h);
// bottom left:
ctx.lineTo(0, h);
};
this.label.setExtent(text.extent().add(this.edge));
this.label.add(text);
this.label.text = text;
};
StagePickerMorph.prototype.createItems = function (scale) {
var item,
x,
y,
isLine = false;
this.scale = scale;
this.children.forEach(m => m.destroy());
this.children = [];
this.edge = SyntaxElementMorph.prototype.rounding * this.scale;
this.border = SpriteMorph.prototype.bubbleBorder * this.scale;
this.color = WHITE;
this.borderColor = SpriteMorph.prototype.blockColor.sensing;
this.setExtent(new Point(0, 0));
y = this.border;
x = this.left() + this.border;
if (this.title) {
this.createLabel();
this.label.setPosition(this.bounds.origin);
this.add(this.label);
y = this.label.bottom();
} else {
y = this.top() + this.edge;
}
y += 1;
this.items.forEach(tuple => {
isLine = false;
if (tuple[0] === 0) {
isLine = true;
item = new Morph();
item.color = this.borderColor;
item.setHeight(tuple[1] * this.scale);
} else {
item = new StagePickerItemMorph(
this.target,
tuple[1],
tuple[0],
SpriteMorph.prototype.bubbleFontSize * this.scale,
null, // MorphicPreferences.menuFontName,
this.environment,
tuple[2], // bubble help hint
tuple[3], // color
tuple[4], // bold
tuple[5], // italic
tuple[6], // doubleclick action
tuple[7] // shortcut
);
}
if (isLine) {
y += 1;
}
item.setPosition(new Point(x, y));
this.add(item);
y = y + item.height();
if (isLine) {
y += 1;
}
});
this.adjustWidths();
this.setExtent(
this.fullBounds().extent().add(new Point(this.border, this.edge))
);
if (this.label) {
this.label.setWidth(this.width());
this.label.text.setPosition(
this.label.center().subtract(
this.label.text.extent().floorDivideBy(2)
)
);
}
};
StagePickerMorph.prototype.maxWidth = function () {
var w = 0;
if (this.parent instanceof FrameMorph) {
if (this.parent.scrollFrame instanceof ScrollFrameMorph) {
w = this.parent.scrollFrame.width();
}
}
this.children.forEach(item => {
if (item instanceof MenuItemMorph) {
w = Math.max(
w,
item.label.width() + this.edge +
(item.shortcut ? item.shortcut.width() + this.border : 0)
);
}
});
if (this.label) {
w = Math.max(w, this.label.width() - this.border);
}
return w;
};
StagePickerMorph.prototype.adjustWidths = function () {
var w = this.maxWidth();
this.children.forEach(item => {
item.setWidth(w);
item.fixLayout();
});
};
// StagePickerMorph removing
StagePickerMorph.prototype.destroy = function () {
MenuMorph.uber.destroy.call(this);
};
// StagePickerMorph submenus
StagePickerMorph.prototype.closeSubmenu = function () {
if (this.submenu) {
this.submenu.destroy();
this.submenu = null;
this.unselectAllItems();
}
};
StagePickerMorph.prototype.rootMenu = function () {
return (this.parent instanceof StagePickerMorph) ?
this.parent.rootMenu()
: this;
};
// StagePickerItemMorph ////////////////////////////////////////////////////////
/*
I am an option that can be clicked inside a StagePickerMorph.
*/
// StagePickerItemMorph inherits from MenuItemMorph:
StagePickerItemMorph.prototype = new MenuItemMorph();
StagePickerItemMorph.prototype.constructor = StagePickerItemMorph;
StagePickerItemMorph.uber = MenuItemMorph.prototype;
// StagePickerItemMorph instance creation:
function StagePickerItemMorph(
target,
action,
labelString, // can also be a Morph or a Canvas or a tuple: [icon, string]
fontSize,
fontStyle,
environment,
hint,
color,
bold,
italic,
doubleClickAction, // optional when used as list morph item
shortcut // optional string, Morph, Canvas or tuple: [icon, string]
) {
this.shortcutString = shortcut || null;
this.shortcut = null;
this.init(
target,
action,
labelString,
fontSize,
fontStyle,
environment,
hint,
color,
bold,
italic,
doubleClickAction
);
this.highlightColor = SpriteMorph.prototype.blockColor.sensing.lighter(75);
this.pressColor = SpriteMorph.prototype.blockColor.sensing.lighter(25);
if (this.shortcut) {
this.shortcut.setColor(SpriteMorph.prototype.blockColor.sensing);
}
}
// StagePickerItemMorph submenus:
StagePickerItemMorph.prototype.popUpSubmenu = function () {
var menu = this.parentThatIsA(MenuMorph),
stage = this.parentThatIsA(StageMorph),
scroller;
if (!(this.action instanceof MenuMorph)) {return; }
this.action.createItems(menu.scale);
this.action.setPosition(this.topRight().subtract(new Point(0, 5)));
this.action.keepWithin(stage);
if (this.action.items.length < 1 && !this.action.title) {return; }
if (this.action.bottom() > stage.bottom()) {
// scroll menu items if the menu is taller than the world
scroller = this.action.scroll();
this.action.bounds.corner.y = stage.bottom() - 2;
scroller.setHeight(
stage.bottom() - scroller.top() - this.action.edge - 2
);
scroller.adjustScrollBars();
}
menu.add(this.action);
menu.submenu = this.action;
this.action.fullChanged();
};

Wyświetl plik

@ -60,11 +60,12 @@ IDE_Morph, ArgLabelMorph, localize, XML_Element, hex_sha512, TableDialogMorph,
StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy, Map,
isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph, BLACK,
TableFrameMorph, ColorSlotMorph, isSnapObject, newCanvas, Symbol, SVG_Costume,
SnapExtensions, AlignmentMorph, TextMorph, Cloud, HatBlockMorph*/
SnapExtensions, AlignmentMorph, TextMorph, Cloud, HatBlockMorph,
StagePickerMorph*/
/*jshint esversion: 6, bitwise: false*/
/*jshint esversion: 6, bitwise: false, evil: true*/
modules.threads = '2022-March-28';
modules.threads = '2022-March-31';
var ThreadManager;
var Process;
@ -3641,36 +3642,67 @@ Process.prototype.doAsk = function (data) {
rcvr = this.blockReceiver(),
isStage = rcvr instanceof StageMorph,
isHiddenSprite = rcvr instanceof SpriteMorph && !rcvr.isVisible,
activePrompter;
activePrompter,
leftSpace,
rightSpace;
stage.keysPressed = {};
if (!this.prompter) {
activePrompter = detect(
stage.children,
morph => morph instanceof StagePrompterMorph
morph => morph instanceof StagePrompterMorph ||
morph instanceof StagePickerMorph
);
if (!activePrompter) {
if (!isStage && !isHiddenSprite) {
rcvr.bubble(data, false, true);
}
this.prompter = new StagePrompterMorph(
isStage || isHiddenSprite ? data : null
);
if (stage.scale < 1) {
this.prompter.setWidth(stage.width() - 10);
if (data instanceof List) {
if (!isStage) {
rcvr.stopTalking();
}
this.prompter = new StagePickerMorph(data);
this.prompter.createItems(stage.scale);
leftSpace = rcvr.left() - stage.left();
rightSpace = stage.right() - rcvr.right();
if (isStage) {
this.prompter.popup(
stage,
stage.center().subtract(
this.prompter.extent().floorDivideBy(2)
)
);
} else {
this.prompter.popup(
stage,
rightSpace > this.prompter.width() ||
rightSpace >= leftSpace ?
rcvr.topRight()
: rcvr.topLeft().subtract(
new Point(this.prompter.width(), 0)
)
);
}
} else {
this.prompter.setWidth(stage.dimensions.x - 20);
if (!isStage && !isHiddenSprite) {
rcvr.bubble(data, false, true);
}
this.prompter = new StagePrompterMorph(
isStage || isHiddenSprite ? data : null
);
if (stage.scale < 1) {
this.prompter.setWidth(stage.width() - 10);
} else {
this.prompter.setWidth(stage.dimensions.x - 20);
}
this.prompter.fixLayout();
this.prompter.setCenter(stage.center());
this.prompter.setBottom(stage.bottom() - this.prompter.border);
stage.add(this.prompter);
this.prompter.inputField.edit();
stage.changed();
}
this.prompter.fixLayout();
this.prompter.setCenter(stage.center());
this.prompter.setBottom(stage.bottom() - this.prompter.border);
stage.add(this.prompter);
this.prompter.inputField.edit();
stage.changed();
}
} else {
if (this.prompter.isDone) {
stage.lastAnswer = this.prompter.inputField.getValue();
stage.lastAnswer = this.prompter.answer;
this.prompter.destroy();
this.prompter = null;
if (!isStage) {rcvr.stopTalking(); }
@ -7586,9 +7618,7 @@ JSCompiler.prototype.compileFunction = function (aContext, implicitParamCount) {
block += value + '=0,';
}
});
/*jshint evil: true*/
return Function('...p', block + code);
/*jshint evil: false*/
};
JSCompiler.prototype.compileExpression = function (block) {