Commit fce32071 authored by Leon Arnott's avatar Leon Arnott
Browse files

Added (after:), a shorthand form of (event:) similar to that in Chapbook.

Notable about this macro design, unlike its (event:) form, is that it contains an optional "skip" parameter similar to that for (t8n-skip:).

Also:
 * Fixed a bug in the previous commit (which should've included <tw-story> as its exception).
 * Fiddled with various parts of the documentation.
 * Tweaked (palette:) to produce less dark link text whenever the produced normal text colour is rather dark.
parent ce60520ec347
......@@ -170,7 +170,8 @@ Documentation is at http://twine2.neocities.org/. See below for compilation inst
* Added a `(text-size:)` style changer macro (also known as `(size:)`) that scales the attached hook's font size and line height by the given multiplier.
* Added an `(opacity:)` style changer macro, which allows the CSS opacity of hooks to be altered. This also allows sidebar icons' opacity to be altered, using (change:) and other tools.
* Added a `(transition-delay:)` macro (also known as `(t8n-delay:)`) which adds an initial delay to transitions before they begin animating. This can only enchant hooks, not links.
* Added a `(transition-skip:)` macro (also known as `(t8n-skip:)`) which, when included with a transition, allows the player to speed up the transition by a given number of milliseconds per frame, by holding down any keyboard key, mouse button or touching the screen.
* Added a `(transition-skip:)` macro (also known as `(t8n-skip:)`) which, when included with a transition, allows the player to speed up the transition by a given number of milliseconds per frame, by holding down any keyboard key, mouse button, or by touching the screen.
* Added an `(after:)` changer macro, which is essentially a `(live:)` changer that only ever runs the hook once. However, unlike `(live:)`, it takes an optional second number that allows the player to speed up the delay by the given number of milliseconds per frame, by holding down any keyboard key, mouse button, or by touching the screen.
* Added a `(collapsed:)` changer macro, which causes the attached hook's whitespace to collapse, as if by the `{` and `}` collapsing whitespace markup. This can also be used with `(enchant:)`.
* Added a `(verbatim:)` changer macro (alias `(v6m:)`), which causes the attached hook or command to be printed verbatim, as if by the verbatim markup.
* Added a `(text-indent:)` changer macro, which applies a leading indent to the attached hook, and can be applied to each line in the passage using, for example, `(enchant:?passage's lines, (text-indent:12))`.
......
......@@ -558,7 +558,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
(link-goto:), (undo:), (loadgame:)
Added in: 1.0.0
#links
#navigation
*/
("go-to",
(name) => {
......@@ -622,7 +622,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
(go-to:), (link-undo:), (icon-undo:)
Added in: 2.0.0
#links
#navigation
*/
("undo",
noop,
......
......@@ -1160,7 +1160,7 @@ define([
If any passage's (storylet:) macro produces an error (such as by dividing a number by 0), it will be displayed when the (open-storylets:) macro is run.
See also:
(storylet:), (passages:)
(storylet:), (link-storylet:), (passages:)
Added in: 3.2.0
#storylet 2
......
......@@ -591,7 +591,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'engine', 'datatypes/c
this produces a command (and not a changer), you can simply attach changers to the front of it to style the link.
Added in: 3.2.0
#links
#links 8
*/
("link-storylet",
/*
......
......@@ -2,6 +2,7 @@
define(['macros','renderer', 'utils/operationutils', 'datatypes/lambda', 'internaltypes/twineerror'], (Macros, Renderer, {clone, objectName, isValidDatamapName}, Lambda, TwineError) => {
/*d:
Metadata data
Certain kinds of macros are not used inside the passage itself, but are used to mark the passage as being special in some way, or having certain
data available to other macros that inspect the story's state, such as (passages:) or (open-storylets:). These macros are "metadata" macros,
because they attach metadata to the passage. These macros must appear at the very start of those passages, ahead of every other kind of macro.
......@@ -38,7 +39,7 @@ define(['macros','renderer', 'utils/operationutils', 'datatypes/lambda', 'intern
Storylets are mini-stories within a story - disconnected sequences of passages that can be visited non-linearly when certain conditions are fulfilled.
They allow a different way of writing interactive fiction than the rigid tree structure of typical Twine games: instead,
simply write scenes and events that occur in the story, use this macro in the first passage of these mini-stories to write a programming condition that
determines when it would make sense for that scene to occur, and use macros like (open-storylets:) to dynamically create links to the storylets.
determines when it would make sense for that scene to occur, and use macros like (open-storylets:) or (link-storylet:) to dynamically create links to the storylets.
This authoring style allows content to be added to the story without having to dramatically rearrange the story's structure.
Examples of narrative structures that can be represented as storylets include: jobs on a job board that are available at different times but only
......@@ -92,6 +93,9 @@ define(['macros','renderer', 'utils/operationutils', 'datatypes/lambda', 'intern
that a storylet with a negative urgency, such as `(urgency: -11)`, will appear at the end of the (open-storylets:) array, unless a storylet
with an even lower urgency is also open.
See also:
(exclusivity:)
Added in: 3.2.0
#storylet 4
*/
......@@ -121,6 +125,9 @@ define(['macros','renderer', 'utils/operationutils', 'datatypes/lambda', 'intern
that a storylet with a negative exclusivity, such as `(exclusivity: -0.001)`, will not be able to appear in (open-storylets:) if any other storylets
lacking an explicit (exclusivity:) call are also open.
See also:
(urgency:)
Added in: 3.2.0
#storylet 3
*/
......
......@@ -402,7 +402,7 @@ define(['jquery','macros', 'utils', 'utils/renderutils', 'datatypes/colour', 'da
milliseconds. If (if:) or (unless:) macros are inside the hook, they of course will be re-evaluated each time.
Details:
Numbers given to macros such as `(live:)` can be suffixed with `ms` or `s` to indicate whether you mean milliseconds or
Numbers given to macros such as (live:) can be suffixed with `ms` or `s` to indicate whether you mean milliseconds or
seconds (see the article on number data for more information). If you give a bare number, the macro interprets it as milliseconds.
Live hooks will continue to re-render themselves until they encounter and print a (stop:) macro. (stop:) should be used
......@@ -410,13 +410,14 @@ define(['jquery','macros', 'utils', 'utils/renderutils', 'datatypes/colour', 'da
with clicking, selecting text, and other interactions).
If you want to just display a hook once a certain thing happens (that is, when the condition in an (if:) macro becomes
true) and then (stop:), then the (event:) macro may be shorter and easier to use for this.
true) and then (stop:), then the (event:) macro may be shorter and easier to use for this. If you want to display a hook after
a certain amount of time has passed, then the (after:) macro is almost certainly what you'd prefer to use.
Currently, you **cannot** attach (live:) to a command (such as in `(live:2s)(link-goto:"?")`). You have to wrap the command
in a hook (such as `(live:2s)[(link-goto:"?")]`).
See also:
(event:)
(event:), (more:), (after:)
Added in: 1.0.0
#live 1
......@@ -509,7 +510,7 @@ define(['jquery','macros', 'utils', 'utils/renderutils', 'datatypes/colour', 'da
(show:), (link-show:)
Added in: 3.1.0
#live 4
#live 5
*/
("more",
() => ChangerCommand.create("more"),
......@@ -521,15 +522,71 @@ define(['jquery','macros', 'utils', 'utils/renderutils', 'datatypes/colour', 'da
As such, the following is a fake "when" Lambda, complete with fake "when" property, which is passed to
runLiveHook() in section.js, as if it was created by Lambda.create().
*/
event: {
when: true,
filter: section => section.eval("Operations").Identifiers.exits !== 0 ? [] : [true],
},
};
},
null
)
/*d:
(after: Number, [Number]) -> Changer
Hooks that have this changer attached will only be run once the given amount of time has passed since the passage was rendered. An
optional second number specifies that the player can speed up the delay by holding down a keyboard key or mouse button, or by touching the touch device.
Example usage:
This example makes 3 additional hooks appear, one by one. The delays can only be sped up if the passage has been visited before.
The `time + 2s` idiom is a convenient way to express that each hook is displayed 2 seconds after the last one was displayed (as the `time` identifier
tracks the time passed since the passage was rendered, not the containing hook).
```
There she was. (after: 2s, (cond: visits > 0, 200ms, 0))[=
Covered in fur, (after: time + 2s, (cond: visits > 0, 200ms, 0))[=
sitting on all fours, (after: time + 2s, (cond: visits > 0, 200ms, 0))[=
and howling at the moon.
```
Rationale:
This macro is a shorthand form of (event:) that only is given an amount of time to wait. `(after:2s)` is the same as `(event: when time > 2s)`.
It is also similar to (live:), except that it only runs the hook at most once.
One significant difference this has over (event:) is that it can offer the player the ability to speed up transitions. Frequently asking the player to wait for timed delays
can be detrimental to the pacing of a story, especially if they are revisiting earlier passages, and providing an option to skip or expedite them is often
greatly appreciated.
Details:
Numbers given to macros such as (after:) can be suffixed with `ms` or `s` to indicate whether you mean milliseconds or
seconds (see the article on number data for more information). If you give a bare number, the macro interprets it as milliseconds.
The optional second number given is an amount of milliseconds (or, if suffixed with `s`, seconds) to advance the transition. For each
millisecond of the transition, Harlowe checks if a key or button is held, and if so, then it is advanced
by the given number (in addition to the elapsed millisecond).
See also:
(live:), (event:), (more:), (transition-skip:)
Added in: 3.2.0
#live 4
*/
("after",
(_, delay, skip) => ChangerCommand.create("after", [delay].concat(skip !== undefined ? [skip] : [])),
(d, delay, skip) => {
d.enabled = false;
d.data.live = {
event: {
when: true,
filter: section => {
return section.eval("Operations").Identifiers.exits !== 0 ? [] : [true];
if (Utils.anyInputDown()) {
delay -= skip;
}
return section.eval("Operations").Identifiers.time > delay ? [true] : [];
},
},
};
},
null
[positiveNumber, optional(nonNegativeNumber)]
)
/*d:
......@@ -789,7 +846,7 @@ define(['jquery','macros', 'utils', 'utils/renderutils', 'datatypes/colour', 'da
of skipping or accelerating the transitions if they so choose.
Details:
The number given is an amount of milliseconds (or seconds, if specified) to advance the transition. For each
The number given is an amount of milliseconds (or, if suffixed with `s`, seconds) to advance the transition. For each
millisecond of the transition, Harlowe checks if a key or button is held, and if so, then it is advanced
by the given number (in addition to the elapsed millisecond).
......
......@@ -859,9 +859,9 @@ define(['macros', 'utils', 'utils/operationutils', 'datatypes/colour', 'datatype
Example usage:
```
{(set: _p to (palette: "mono", orange+black))
(enchant: ?page, (background: _p's 1st)+(text-colour: _p's 2nd))
(enchant: ?link, (colour: _p's 3rd)+(hover-style:(colour:_p's 4th)))}
{(set: _p to (palette: "mono", orange + black))
(enchant: ?page, (background: _p's 1st) + (text-colour: _p's 2nd))
(enchant: ?link, (colour: _p's 3rd) + (hover-style: (colour:_p's 4th)))}
This passage uses (link:"(more)")[a brown palette.]
```
......@@ -907,6 +907,13 @@ define(['macros', 'utils', 'utils/operationutils', 'datatypes/colour', 'datatype
*/
text = Colour.create(lcha);
lcha.l += (l <= 0.75 ? -0.1 : 0.1);
/*
To push the link text further away from black if the plain text is already
relatively black, this important nudge is applied.
*/
if (lcha.l < 0.5) {
lcha.l *= 0.5/lcha.l;
}
link = Colour.create(lcha);
lcha.l += (l <= 0.85 ? 0.15 : -0.15);
hover = Colour.create(lcha);
......
......@@ -478,7 +478,7 @@
Like most passage text markup, this cannot be used inside a macro call (for instance, `(print: \`<br>
`3)`) - but since line breaks between values in macro calls are ignored, this doesn't matter.
#whitespace
#whitespace 4
*/
escapedLine,
......@@ -658,7 +658,7 @@
If you only want to remove specific line breaks, consider the escaped line break markup.
#whitespace
#whitespace 2
*/
collapsedFront: "{",
collapsedBack: "}",
......@@ -857,7 +857,7 @@
All of the details pertaining to the collapsing markup apply here - consult its article for more information.
#whitespace 2
#whitespace 3
*/
unclosedCollapsed: "\\{=+",
......
......@@ -336,6 +336,8 @@ define(['jquery', 'markup', 'utils/polyfills'],
*/
buttonsDown: (...buttons) => buttons.every(b => buttonsHeld[b]),
anyInputDown: () => keysHeldCount + buttonsHeldCount > 0,
/*
Provides very direct access to mouseCoords.
*/
......@@ -529,13 +531,17 @@ define(['jquery', 'markup', 'utils/polyfills'],
Note that the other two elements that have display:flex, <tw-story> and <tw-columns>, don't have this concern due to their children
being part of usuallyBlockElements.
*/
if (el.parent().is('tw-backdrop') || Utils.childrenProbablyInline(el)) {
if (Utils.childrenProbablyInline(el)) {
/*
If there are no element children of the container (only text), simply use 'inline'.
*/
el.css('display','inline' + (el.children().length ? '-block' : ''));
}
else {
/*
Special case: due to <tw-backdrop> and <tw-story> having display:flex, getComputedStyle() will always report 'display:block' for
each child element of it, even though flex elements are positioned differently and don't need width:100%.
*/
else if (!el.parent().is('tw-backdrop,tw-story')) {
/*
Both of these are necessary for overriding "display:inline-block" for certain transitions,
including those that use transform.
......@@ -599,19 +605,17 @@ define(['jquery', 'markup', 'utils/polyfills'],
this code, which gives the transition element the correct display property, is crudely placed in a delayed timeout.
*/
requestAnimationFrame(() => {
/*
Special case: due to <tw-backdrop> having display:flex, getComputedStyle() will always report 'display:block' for
each child element of it, even if it's a <tw-dialog>. So, ignore childrenProbablyInline() and simply assume that block positioning mustn't be applied.
Note that the other two elements that have display:flex, <tw-story> and <tw-columns>, don't have this concern due to their children
being part of usuallyBlockElements.
*/
if (el.parent().is('tw-backdrop') || Utils.childrenProbablyInline(el)) {
if (Utils.childrenProbablyInline(el)) {
/*
If there are no element children of the container (only text), simply use 'inline'.
*/
el.css('display','inline' + (el.children().length ? '-block' : ''));
}
else {
/*
Special case: due to <tw-backdrop> and <tw-story> having display:flex, getComputedStyle() will always report 'display:block' for
each child element of it, even though flex elements are positioned differently and don't need width:100%.
*/
else if (!el.parent().is('tw-backdrop,tw-story')) {
/*
Both of these are necessary for overriding "display:inline-block" for certain transitions,
including those that use transform.
......
......@@ -6,18 +6,18 @@ So, I worked diligently on Harlowe for 9 months. In retrospect, it feels like my
And now you get to enjoy all of this, and more.
* A means of creating custom macros, using (macro:), (output:) and (output-hook:)
* String patterns, an advanced means of matching strings comprehensively, that can be used with `matches` and the new (split:) and (trimmed:) macros
* Changers for creating block elements, (box:) and (float-box:), with the latter allowing you to overlay a box of text anywhere in the browser window
* Support for storylets as an alternative to hard-coded links between sections of a game, provided by (storylet:), (open-storylets:), and their related macros
* Macros for adding and creating the sidebar's icons, allowing you to freely customise it using (replace:) and other macros
* Several new interaction/UI command macros, such as (input-box:), (checkbox:) and (meter:)
* (set:), (put:) and (move:) now support optional type-restrictions on variables
* A changer and a command for displaying text verbatim, (verbatim:) and (verbatim-print:)
* Interaction macros for toggling fullscreen mode, (link-fullscreen:) and (icon-fullscreen:)
* Assorted pieces of "missing" common functionality, like (rerun:), (link-rerun:), (text-size:), (border:), (opacity:) and (joined:)
* A small selection of debugging-focused macros, such as (ignore:), (test-false:), (test-true:) and (assert:)
* Additional features for Debug Mode, including an enchantments panel, buttons for producing the source code of variables, and a DOM View button that shows the basic HTML DOM structure of the page
* A means of creating custom macros, using (macro:), (output:) and (output-hook:).
* String patterns, an advanced means of matching strings comprehensively, that can be used with `matches` and the new (split:) and (trimmed:) macros.
* Changers for creating block elements, (box:) and (float-box:), with the latter allowing you to overlay a box of text anywhere in the browser window.
* Support for storylets as an alternative to hard-coded links between sections of a game, provided by (storylet:), (open-storylets:), and their related macros.
* Macros for adding and creating the sidebar's icons, allowing you to freely customise it using (replace:) and other macros.
* Several new interaction/UI command macros, such as (input-box:), (checkbox:) and (meter:).
* (set:), (put:) and (move:) now support optional type-restrictions on variables.
* A changer and a command for displaying text verbatim, (verbatim:) and (verbatim-print:).
* Interaction macros for toggling fullscreen mode, (link-fullscreen:) and (icon-fullscreen:).
* Assorted pieces of "missing" common functionality, like (rerun:), (link-rerun:), (text-size:), (border:), (opacity:) and (joined:).
* A small selection of debugging-focused macros, such as (ignore:), (test-false:), (test-true:) and (assert:).
* Additional features for Debug Mode, including an enchantments panel, buttons for producing the source code of variables, and a DOM View button that shows the basic HTML DOM structure of the page.
* Heavily improved in-editor syntax highlighting that now colours macros by their datatype and can, if enabled, show tooltips for code structures.
And, most importantly, a new toolbar has been implemented for the Twine 2 editor that allows common Harlowe code idioms to be quickly created.
......
......@@ -149,6 +149,13 @@ const
defCode: "markup",
regExp: /^\s*([\w ]+) markup\n/,
navLink(def) {
// Hardcoded for 3.2.0
const isNew = def.name === "Unclosed collapsing whitespace";
return `<li><a href="#${def.anchor}" ${isNew ? "class='nav_new'": ""}>${def.name}</a></li>`;
},
definition({input, 0:title, 1:name}) {
const slugName = name.replace(/\s/g,'-').toLowerCase();
let text = input.trim().replace(title, "\n<h2 class='def_title markup_title' id=markup_" + slugName + ">"
......@@ -169,6 +176,13 @@ const
defCode: "type",
regExp: /^\s*([\w]+) data\n/,
navLink(def) {
// Hardcoded for 3.2.0
const isNew = ["CodeHook","CustomMacro","Metadata","TypedVar"].includes(def.name);
return `<li><a href="#${def.anchor}" ${isNew ? "class='nav_new'": ""}>${def.name}</a></li>`;
},
definition({input, 0:title, 1:name}) {
const slugName = name.replace(/\s/g,'-').toLowerCase();
let text = input.trim().replace(title, "\n<h2 class='def_title type_title' id=type_" + slugName + ">"
......
......@@ -64,6 +64,30 @@ describe("live macros", function() {
},20);
});
});
describe("the (after:) macro", function() {
it("requires a positive number, and an optional non-negative number", function() {
expect("(after:)[]").markupToError();
expect("(after:1)[]").not.markupToError();
expect("(after:1,1)[]").not.markupToError();
expect("(after:0)[]").markupToError();
expect("(after:1,-0.1)[]").markupToError();
});
it("doesn't immediately display the hook", function(done) {
var p = runPassage("(after: 12s)[baz]");
setTimeout(function() {
expect(p.text()).not.toBe('baz');
done();
},20);
});
it("displays the attached hook only when the given time has elapsed", function(done) {
var p = runPassage("(after:30ms)[bar]");
expect(p.text()).toBe("");
setTimeout(function() {
expect(p.text()).toBe("bar");
done();
},40);
});
});
describe("the (more:) macro", function() {
it("takes no arguments", function() {
expect("(more:)[]").not.markupToError();
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment