Dialog Wishlist

Also, an actual bug squashed—those are becoming few and far between now!

Previously, exiting one span into another when both have different non-default styles would produce spurious spaces in the debugger. Now it won’t. Look at the changes in that PR to see the improvements in the test cases!

(I realize this excites no one but me, but it feels very good to fix an outright bug from before we took over the project.)

3 Likes

(currently transcribing) or the like?

2 Likes

Based on some discussion in other threads:

Dialog currently has no way to set the overall CSS of the game, outside of any divs and spans. This would be nice for things like the background color changes in The Van der Nagel Papyrus or the global text color changes in Photopia. It would also allow authors to embed an overall color scheme in the game itself, rather than editing the Å-machine web interpreter’s CSS.

I’m imagining something like (global style $) that applies a style class to the entire screen (minus any status bars). For simplicity, this predicate would be illegal inside a div or span; whether it applies to existing text is undefined (Z-machine no, web yes), so it’s safest to (clear) immediately after.

On the Å-machine, this would require a new opcode; I propose $ED, which is in the “output control” range ($6X and $EX) and not currently defined. On the Z-machine, it would just involve setting some registers.

What do you all think?

  • This is a good idea!
  • This is a bad idea.
  • I don’t care.
0 voters
1 Like

I actually ran into this and had to put a (no space) before the closing brace of a continuation.

1 Like

Something that may have been on someone’s wish list: I looked into making Dialog compile to Z-machine version 3. The standard library would probably be too large, but non-library projects could fit.

Unfortunately, it’s not possible. The (stop) builtin is used to bail out of a bunch of nested routines at once, and the Z-machine can only do that in version 5 onward (via the @throw and @catch opcodes). Even in programs that avoid (stop), the same mechanism is sometimes used for (just) (fail) and the like.

1 Like

By analogy, there should also be a predicate to reset the global style, but I’m hesitant to use up another Å-machine opcode for something so niche. It seems easier, if less elegant, to just define an empty style class and use that.

I guess I could make the compiler add an empty style class to the .aastory file if (reset global style) is ever called. That would be a bit more work on the compiler’s side, but would save the opcode.

2 Likes

All right, predicates are implemented (but not yet documented), named (body style $) and (reset body style). On Z-machine and Å-machine web frontend, they work as expected; in Node, 6502 (C64 and aambox), and debugger, they do nothing.

This is a fairly big UI change, so I’d appreciate if anyone could play with the output and tell me what you think. In particular, trying to set the global color and background-color in the web interpreter sometimes acts strangely if it fights with the light mode/dark mode CSS. The documentation will explain how to work with it rather than against it.

Here’s that documentation:

It is also possible to set the “body style” of the story: the style used outside of any divs and spans. This is a dangerous power, and is less widely supported than ordinary divs and spans; results may vary from interpreter to interpreter. Still, adjusting the background color of the page can be an effective way to mark a dramatic shift in the game, or just to set a more interesting mood than the default black and white.

To set a new body style:

(body style $Class)

Or undo it again:

(reset body style)

The latter is equivalent to setting the body style to an empty style class, and in fact, that’s how it is implemented under the hood. Whether a new body style applies to existing text depends on the interpreter, so it is safest to (clear) the screen after changing the body style.

In theory, a body style can use all the same properties as a div or span style, but in practice, color and background-color are the most useful. These, though, come with their own set of caveats. Most Z-machine interpreters only apply a new background color after (clear)ing the screen—but some, like Gargoyle, apply a new background color at the next (get key $) or (get input $) instead. Gargoyle also applies a body style’s color to existing text, but only if that text has no color of its own (from a div, span, or previous body style); other interpreters do not.

On the Å-machine, only the web interpreter supports body styles. It applies them to the entire screen (including existing text) immediately, without waiting for a (clear). Setting the color and background-color, though, may produce unexpected results. If the user has set dark mode, either explicitly in the interpreter options or implicitly in their browser settings, that may override the color and background-color—or worse, only one of the two!

For this interpreter specifically, a set of custom properties are provided, to work with the user’s dark mode setting rather than against it:

--black: #000000; // Used for overall background in night mode and text in day mode
--white: #eeeeee; // Used for overall background in day mode and menu options when hovered 
--darkgray: #222222; // Used for transcript text/background depending on mode 
--medgray: #888888; // Used for the menu lines, "close" button on the transcript, and "about" background 
--lightgray: #cccccc; // Used for text in night mode, menu background, progress bar outline 
--offwhite: #dddddd; // Used for menu lines and "close" button hover, transcript text/background 
--progtop: #d3d3d3; // Top half of the progress bar 
--progbot: #c4c4c4; // Bottom half of the progress bar 
--link: #875; // Links 
--alink: #aa8; // Hovered links 

These properties are not recognized by the other backends.

In the web interpreter, it sets a new class on the <body> element, which can have very unintuitive interactions with the interpreter’s built-in CSS. This is the best way I could find to explain it.

Still, it should support the main two use cases:

  • Supplying an overall color scheme for the story
  • Switching the background color like in VdN Papyrus
2 Likes

Well, this is a bizarre one.

(program entry point)
	(temp 1)

(temp 1)
	(show [ {A} {B} {C} ])

(show $X) $X

Should output:

[[0] [1] [2]]

But actually outputs:

[[0] [[[2]]] [2]]

Some temporary variable seems to be getting misplaced. Every element of this program seems to be necessary: the bug does not happen if the calls to (temp $) or (show $) are inlined, or if (temp $) does not have a parameter.

Here’s the IR that’s being produced:

Intermediate code for (temp $): 0 -1
R0: (group leader) (1 incoming) clause 65535
	JUMP                      R1           -            -           
R1: (part of group R0) (1 incoming) clause 65535
	ALLOCATE                  0            1            -           
	UNIFY                     A0           1            -           
	TRACEPOINT 0              closurebug.dg:4
	MAKE_PAIR_VV              X0           2            []          
	MAKE_PAIR_VV              A0           X0           []          
	MAKE_PAIR_VV              X1           A0           []          
	MAKE_PAIR_VV              A0           X1           A0          
	MAKE_PAIR_VV              X2           0            []          
	MAKE_PAIR_VV              A0           X2           A0          
	TRACEPOINT 1              closurebug.dg:5
	SET_CONT                  R2           -            -           
	INVOKE_ONCE               (show $)
R2: (group leader) (1 incoming) clause 65535
	TRACEPOINT 3              closurebug.dg:5
	DEALLOCATE 1              -            -            -           
	PROCEED 0                 -            -            -           

It happens on all backends, so the problem must be in the frontend.

Technical details

Doesn’t have to be closures, either; this has the same problem.

(program entry point)
	(temp 1)

(temp 1)
	(show [ [1] [2] [3] ])

(show $X) $X
[[[[2] [3]]] [2] [3]] 

The argument to (temp $) has to be the same as one of the elements in the lists:

(program entry point)
	(temp @apple)

(temp @apple)
	(show [ [apple] [banana] [cantaloupe] ])

(show $X) $X
[[[[banana] [cantaloupe]]] [banana] [cantaloupe]]
(program entry point)
	(temp @banana)

(temp @banana)
	(show [ [apple] [banana] [cantaloupe] ])

(show $X) $X
[[apple] [[[cantaloupe]]] [cantaloupe]]

And it has to be a literal. This is fine:

(program entry point)
	(temp @banana)

(temp $)
	(show [ [apple] [banana] [cantaloupe] ])

(show $X) $X
[[apple] [banana] [cantaloupe]]

How bizarre!

This seems to be the minimal example:

(program entry point)
	(temp 1)

(temp 1)
	(show [ [1] [2] ])

(show $X) $X

The broken IR:

Intermediate code for (temp $): 0 -1
R0: (group leader) (1 incoming) clause 65535
	JUMP                      R1           -            -           
R1: (part of group R0) (1 incoming) clause 65535
	ALLOCATE                  0            1            -           
	UNIFY                     A0           1            -           
	TRACEPOINT 0              closurebug.dg:4
	MAKE_PAIR_VV              X0           2            []          
	MAKE_PAIR_VV              A0           X0           []          
	MAKE_PAIR_VV              X1           A0           []          
	MAKE_PAIR_VV              A0           X1           A0          
	TRACEPOINT 1              closurebug.dg:5
	SET_CONT                  R2           -            -           
	INVOKE_ONCE               (show $)
R2: (group leader) (1 incoming) clause 65535
	TRACEPOINT 3              closurebug.dg:5
	DEALLOCATE 1              -            -            -           
	PROCEED 0                 -            -            -           

Working IR (with 3 instead of 1):

R0: (group leader) (1 incoming) clause 65535
	JUMP                      R1           -            -           
R1: (part of group R0) (1 incoming) clause 65535
	ALLOCATE                  0            1            -           
	UNIFY                     A0           1            -           
	TRACEPOINT 0              closurebug.dg:4
	MAKE_PAIR_VV              X0           2            []          
	MAKE_PAIR_VV              A0           X0           []          
	MAKE_PAIR_VV              X1           3            []          
	MAKE_PAIR_VV              A0           X1           A0          
	TRACEPOINT 1              closurebug.dg:5
	SET_CONT                  R2           -            -           
	INVOKE_ONCE               (show $)
R2: (group leader) (1 incoming) clause 65535
	TRACEPOINT 3              closurebug.dg:5
	DEALLOCATE 1              -            -            -           
	PROCEED 0                 -            -            -           

So the working code does this:

X = [2]
A = [X] = [[2]]
Y = [3]
A = [X|A] = [[3] [2]]

And the broken code does this:

X <- [2]
A <- [X] = [[2]]
Y <- [A] = [[[2]]]
A <- [X|A] = [[2] [[2]]]

It looks like it’s using [A] instead of [1] when building the list, even though A has already been given a new value and is no longer 1.

The only place a MAKE_PAIR_VV instruction is emitted is in comp_value_into in compile.c. To make each of its arguments, it calls comp_value(cl, an->children[i], seen, known_args). That routine checks if the value it’s compiling is equivalent to any of the known_args, and if so, it uses that argument’s value to cut down on temporary variables.

So the problem is that the slot in known_args isn’t getting cleared out when the argument gets reassigned!

		if(dest.tag == OPER_ARG) { // See issue #204
			known_args[dest.value] = NULL;
		}

Problem solved. Haha!

Tada! Now, back to messing with global styles.

Reading this documentation, I don’t understand how to set my own colour schemes in the web interpreter that won’t end up unreadable if they’re overridden by the user’s dark mode setting. My best interpretation of that text is that that isn’t possible unless I use the provided colour scheme. Am I getting that right?

If you set a color scheme the Z-machine way:

(style class @body)
    color: green;
    background-color: black;

Then it’ll interact weirdly with the dark mode setting. I’m trying to find a way to fix that, but unfortunately my grasp of CSS isn’t the best.

So what I currently recommend is using the custom properties instead:

(style class @body)
    --black: black;
    --white: mint;
    --lightgray: green;
    %% set darkgray, medgray, offwhite too if you want

Now dark mode will be green on black, and light mode will be black on mint, because dark mode and light mode refer back to those properties when they set color and background-color.

I’m hoping to find some way I can support both (the basic properties and the custom properties), and I think it should be possible, but I have to really grok how CSS handles conflicting values for the same property at the same priority level.

Fixed this bug while I’m at it. Fans of accumulating into constant numbers, rejoice!

2 Likes

After sleeping on it, I think the problem is really that the HTML of the web interpreter is a mess. The HTML file itself is basically empty, only loading the JS and CSS, and the JS then builds the HTML element-by-element on its own.

Which makes it really hard to mess with the HTML structure! So I’m going to try to refactor this at some point (might not be for 1.0.0–1b/01), at which point I won’t have to use weird workarounds in the CSS. Then I can make sure the properties apply at exactly the places I want them to.

At that point, the behavior will be:

  • Setting the standard color and background-color properties will work just like on Z-machine, changing the colors of the entire page. This will override the user’s choice of light or dark mode, but UI elements like links, progress bars, and the “about” menu will keep their normal colors (which will obey light/dark mode).
  • Setting the non-standard color palette properties, --black, --white, and so on, will change the colors of the entire page, including the UI. The light/dark mode switch will control whether the page is --white on --black (dark mode) or --black on --lightgray (light mode), but what exactly --white etc mean is controlled by the author’s style.

Does that seem like a sensible way to go about it? I’m half-tempted to add a “prefers dark mode” option to VM_INFO, but I think it’s easier to just let the interpreter handle that, separate from the game. The game decides what colors are in the palette; the interpreter decides how to apply those colors based on the user’s preferences.

(I’m also not fully happy with --black, --white, and so on as the custom property names, but I can’t think of a better way to name six different shades of lightness. I don’t want to convey that --black always has to be #000000, but I also want to make it clear that --lightgray should be much brighter than --darkgray and somewhat dimmer than --white. If you have ideas, please share!)

This seems likely to cause conflicts. If important parts of the page can be either light or dark, depending on something outside the game’s control, that limits the range of background colors the game can use without rendering them unreadable. Maybe they can switch between light and dark mode based on the game’s choice of background color, instead of the user’s preference?

darkest, darker, dark, light, lighter, lightest
lightness-1 through lightness-6

Is there a reason you can’t have properties for --darkmode-bg, --lightmode-bg, --lightmode-transcript etc?

Oh, my intent is to make sure they stay readable no matter what—anything that doesn’t respond to the (body style $) predicate will have both a foreground color and a background color set, so it’s never (e.g.) putting black text over an unknown background. That seems safer than trying to programmatically figure out if a given background is “light” or “dark”.

Those are decent options! But…

I think marking them semantically like this will be better. My current thought is:

  • --main-dark is the background in dark mode and the text in light mode
  • --main-light is the background in light mode (should be dimmer than main-lighter)
  • --main-lighter is the text in dark mode (should be brighter than main-light)
  • --ui is UI components like the menu button (should be visible against both main-dark and main-light)
  • --ui-dark is the transcript and menu background/text
  • --ui-light is the transcript and menu background/text
  • --ui-active is UI components when hovered (should be visible against both ui-dark and ui-light)
  • --accent is normal links and the bottom of the progress bar (should be visible against both main-dark and main-light, and distinct from both main-dark and main-lighter)
  • --accent-active is hovered links and the top of the progress bar

This consolidates a few things that were previously separate, like links and progress bar combined into a single “accent” category, but through some CSS wizardry I can let people customize those separately too if they want.

Does that sound reasonable? All of these except --accent[-active] will default to shades of gray, which should look all right with most color schemes if authors don’t want to customize it fully.

2 Likes

I’ve started work on this, because I don’t think I can get body styling working without this change. Imagining the HTML in my head clearly enough to write CSS for it is beyond my current abilities.

Previously:

<body>
	<div id="aacontainer"></div>
</body>

Now:

HTML
<body> <!-- Where "night" and "enlarge" classes attach -->
<div id="aabody"> <!-- Where (body style $) attaches -->
	<div id="aacontainer"> <!-- Central (width-limited) column -->
		<div id="aaouterstatus"> <!-- Top bar -->
			<div id="aamenubutton"> <!-- Menu bottom (top right) -->
				<div id="aamenulines"></div> <!-- Graphic -->
				<div id="aamenu"> <!-- The menu itself -->
					<div id="aamenulist"> <!-- List of options and links -->
						<div id="aacheckboxes"></div> <!-- Filled in by JS -->
						<hr /> <!-- Onclick for all of these is added by JS -->
						<div id="aaviewscript" class="aamenuoption">View transcript</div>
						<div id="aasavescript" class="aamenuoption">Save transcript</div>
						<div id="aarestart" class="aamenuoption">Restart game</div>
						<div id="aasavestory" class="aamenuoption">Download story file</div>
						<hr />
						<div id="aaaboutopen" class="aamenuoption">About</div>
					</div>
				</div>
			</div>
			<div id="aastatus"></div> <!-- Filled in by game -->
		</div>
		<div id="aastatusborder"></div> <!-- Bar between status and game -->
		<div id="aaaboutouter"> <!-- "About" overlay -->
			<div id="aaaboutinner">
				<div id="aaaboutmeta" class="aaaboutline"></div> <!-- Filled in by JS -->
				<hr />
				<div class="aaaboutline">
					<a id="aaaboutlink" target="_blank" href="https://github.com/Dialog-IF/aamachine/">&Aring;-machine web interpreter v1.0.0</a>
				</div>
				<hr />
				<div class="aaaboutline">
					<div id="aaaboutclose" class="aailink">Close</div>
				</div>
			</div>
		</div>
		<form id="aaform" autocomplete="off"> <!-- To handle keypresses etc -->
			<div id="aamain" aria-live="polite"> <!-- Main body of the game -->
				<input id="aainput" type="text" value="" autocomplete="off" spellcheck="false" autocorrect="off" aria-live="off" />
			</div>
			<div id="aascriptouter"> <!-- Transcript -->
				<textarea id="aascriptinner" readonly></textarea>
				<div id="aascriptclose">Close transcript</div>
			</div>
		</form>
		<div id="aaerrorouter"> <!-- Error messages -->
			<div id="aaaboutinner"> <!-- Pretty sure this is a mistake in the original JS: why are there two divs with the same ID? -->
				<div id="aaerrorlog"></div>
				<div id="aaaboutline">
					<div id="aaerrorclose" class="aailink">Close</div>
				</div>
			</div>
		</div>
	</div>
</div>
</body>

Once I’m confident that this is working, I’ll start rejiggering the CSS. The current system works, but it’s fragile, and I’d like to make it more robust so it doesn’t crash and burn once authors start applying their own styles to it.

1 Like

Question, I generally just rely on an interpreter to handle the styling. What precise purpose is to be found in CSS styling baked into the language? (Genuine question)