0 votes
by (790 points)
edited by

Is it possible to create a variant of linkappend, where, instead of having to click on a link to display text below, the player just needs to move his/her cursor over the link?

 

Edit: There's this answer but it brings the player to the next passage instead of appending text.

2 Answers

0 votes
by (660 points)

What format (and version) are you using?

I'm using Harlowe 2.1.0 and the code to use is explained here:

https://twine2.neocities.org/#macro_mouseover-append

by (790 points)
I'm using sugarcube 2.210 (it's stated in the tag).
0 votes
by (159k points)
edited by

EDIT: Replaced my version of the point 1 code example with that supplied by TheMadExile.

NOTE: The code of the follow solution is heavily based on the source code of the SugarCube 2 <<linkappend>> related macros, you should thank TheMaxExile for all the good parts, and blame me for any bugs that I have introduced due to any lacking in my Javascript knowledge.

1. The following Javascript example creates three new custom macros named: <<hoverappend>>, <<hoverprepend>>, and <<hoverreplace>>. This code should be placed within your Story Javascript area.

The three new macros work very similar to the associated <<linkappend>> related macros except that the TwineScript contain within their body is executed the first time the mouse cursor is moved over the related "Link Text" instead of upon selection of it.

/*
	<<hoverappend>>, <<hoverprepend>>, & <<hoverreplace>>
*/
Macro.add(['hoverappend', 'hoverprepend', 'hoverreplace'], {
	isAsync : true,
	tags    : null,
	handler : function () {
		if (this.args.length === 0) {
			return this.error('no hover text specified');
		}

		// Custom debug view setup.
		if (Config.debug) {
			this.createDebugView(
				this.name,
				this.source + this.payload[0].contents + '<</' + this.name + '>>''
			);
		}

		const $hover     = jQuery(document.createElement('span'));
		const $insert    = jQuery(document.createElement('span'));
		const transition = this.args.length > 1 && /^(?:transition|t8n)$/.test(this.args[1]);

		$hover
			.wikiWithOptions({ profile : 'core' }, this.args[0])
			.addClass('link-hover macro-' + this.name)
			.one('mouseenter', (function (macroName, contents) {
				return this.createShadowWrapper(function (event) {
					if (macroName === 'hoverreplace') {
						$hover.remove();
					}
					else {
						$hover.removeClass('link-hover');
					}

					if (contents !== '') {
						const frag = document.createDocumentFragment();
						new Wikifier(frag, contents);
						$insert.append(frag);
					}

					if (transition) {
						setTimeout(function () {
							$insert.removeClass('macro-' + macroName + '-in');
						}, Engine.minDomActionDelay);
					}
				});
			})(this.name, handler.payload[0].contents))
			.appendTo(this.output);

		$insert.addClass('macro-' + this.name + '-insert');

		if (transition) {
			$insert.addClass('macro-' + this.name + '-in');
		}

		if (this.name === 'hoverprepend') {
			$insert.insertBefore($hover);
		}
		else {
			$insert.insertAfter($hover);
		}
	}
});

2. The following CSS is used to style the Link Text of the new macros, it should be placed within your Story Stylesheet area. You should change this CSS to suit your needs.

.link-hover {
    cursor: pointer;
    color: orange;
    text-decoration: none;
    -webkit-transition-duration: .2s;
    -o-transition-duration: .2s;
    transition-duration: .2s;
}

3. The following TwineScript demonstrates how to use the new macros within a Passage.

Some text with a <<hoverappend "link">> that displays this text ''after'' the link once hovered over.<</hoverappend>>

Some text with a <<hoverprepend "link">> that displays this text ''before'' the link once hovered over.<</hoverprepend>>

Some text with a <<hoverreplace "link">> that displays this text ''instead of'' the link once hovered over.<</hoverreplace>>

 

by (68.6k points)

I'd suggest one, relatively, small change.  Instead of keeping a reference to the entire macro execution context, via this, in the call to .one(), I'd simply use a capture to hold onto the two pieces of data you need—the macro's name and contents.  For example:

			.one('mouseenter', ((macroName, content) => this.createShadowWrapper(function (event) {
				if (macroName === 'hoverreplace') {
					$hover.remove();
				}
				else {
					$hover.removeClass('link-hover');
				}

				if (content !== '') {
					const frag = document.createDocumentFragment();
					new Wikifier(frag, content);
					$insert.append(frag);
				}

				if (transition) {
					setTimeout(() => $insert.removeClass(`macro-${macroName}-in`), Engine.minDomActionDelay);
				}
			}))(this.name, handler.payload[0].contents))

 

by (159k points)

@TheMadExile: Is there still an issue with including ES6 anonymous function syntax in examples?

I remember you warned me about doing that in this comment which is why I replaced them in the example I gave. I understand it was due to the potential version of Twine 2 an end-user was running, and that some people are still not running the latest version of Twine 2.

by (68.6k points)
edited by

I, generally, do not recommend using syntax from >=ES2015 since it cannot be polyfilled and old (i.e.pre-ES2015) browsers are still in use.  That said, those browsers are probably rare enough now that I don't bother making a fuss about it, as I used to do.

The referenced comment was related to Twine 2 itself, which, at the time, used a version of NW.js which was not >=ES2015 compatible.  That, at least, has definitely changed.

Also.  I only used an arrow function for the capture IIFE within my example because you do still have several bits of ES2015 syntax within yours; the shorthand method syntax, one arrow function (see the transition timeout callback), and several uses of template strings.

[EDIT] Still, better safe than sorry, so here's a completely ES5 compatible version:

/*
	<<hoverappend>>, <<hoverprepend>>, & <<hoverreplace>>
*/
Macro.add(['hoverappend', 'hoverprepend', 'hoverreplace'], {
	isAsync : true,
	tags    : null,
	handler : function () {
		if (this.args.length === 0) {
			return this.error('no hover text specified');
		}

		// Custom debug view setup.
		if (Config.debug) {
			this.createDebugView(
				this.name,
				this.source + this.payload[0].contents + '<</' + this.name + '>>''
			);
		}

		const $hover     = jQuery(document.createElement('span'));
		const $insert    = jQuery(document.createElement('span'));
		const transition = this.args.length > 1 && /^(?:transition|t8n)$/.test(this.args[1]);

		$hover
			.wikiWithOptions({ profile : 'core' }, this.args[0])
			.addClass('link-hover macro-' + this.name)
			.one('mouseenter', (function (macroName, contents) {
				return this.createShadowWrapper(function (event) {
					if (macroName === 'hoverreplace') {
						$hover.remove();
					}
					else {
						$hover.removeClass('link-hover');
					}

					if (contents !== '') {
						const frag = document.createDocumentFragment();
						new Wikifier(frag, contents);
						$insert.append(frag);
					}

					if (transition) {
						setTimeout(function () {
							$insert.removeClass('macro-' + macroName + '-in');
						}, Engine.minDomActionDelay);
					}
				});
			})(this.name, handler.payload[0].contents))
			.appendTo(this.output);

		$insert.addClass('macro-' + this.name + '-insert');

		if (transition) {
			$insert.addClass('macro-' + this.name + '-in');
		}

		if (this.name === 'hoverprepend') {
			$insert.insertBefore($hover);
		}
		else {
			$insert.insertAfter($hover);
		}
	}
});

 

...