leopard-js / leopard

Library for making Scratch-like projects with JavaScript.
https://leopardjs.com
MIT License
138 stars 26 forks source link

Implement fill-viewport and/or fill-screen behavior #193

Open towerofnix opened 6 months ago

towerofnix commented 6 months ago

As @adroitwhiz comments we already have support for rendering smoothly to any canvas dimensions, so we only need to implement however stage ends up filling either or both the viewport (i.e. browser window) and screen (ala YouTube). Some thoughts:

  1. This could be a button that appears on the template generated (for CodeSandbox) which toggles filling the viewport.
    • This is useful while editing as we can retain the fill-viewport state between reloads.
    • Technically this is what Scratch does as well. However, it's not what kids expect a fullscreen button to do, particularly in the context of CodeSandbox, where we can't expand past the iframe containing the project.
    • This might be appropriate behavior in PullJosh/leopard-website#38, but it probably isn't here.
    • If we have a button like this, we need to make sure the state is retained across reloads (otherwise there's no point at all). I haven't looked into if it's feasible to access localStorage, per-tab storage of some kind, etc in a CodeSandbox/iframe context yet.
  2. This could be a button which toggles entering proper full-screen mode.
    • This is almost useless while editing because AFAIK CodeSandbox reloads its iframe to view changes, which would kick you out of fullscreen, and require a user interaction to re-initiate fullscreen.
    • However, most users won't be affected by the reloading issue because they'll be in fullscreen on the same monitor as includes the editor, so aren't going to have access to the editor.
    • Entering a big fancy proper full screen is probably what users want out of a fullscreen button. It's the nicest way to present a project you're working on to an in-person colleague, for example.
  3. This could be two buttons, one for filling viewport, one for filling screen.
    • But this would be kind of ridiculous complexity, particularly in a barebones UI — no one wants to second guess themselves if they're clicking the correct "make the stage bigger!!1" button.

Particularly if we have an "enter fullscreen" button and not a "fill viewport" button, here are some options for getting the fill-viewport behavior as well as proper screen filling (via button):

  1. This could be magic, i.e. detecting a popped-out window and applying fill-viewport only then.
    • It's dubious if it's actually possible to detect this though, at least in iframe terms.
    • Alternatively, we could snap to 480x360 if the viewport is larger but still fairly close to 480x360, and otherwise expand to viewport dimensions. (taking care for internal document padding obviously)
  2. We could configure filling viewport as part of the generated project code.
    • Maybe the default (in generated code) is to always fill, but users can disable it if they want.
    • I don't know that it makes sense to have this kind of "meta" configuration built into the actual code that a user is editing, but we already do it for project framerate (and I believe stage dimensions), so it might be fairly natural.
  3. We could altogether disable rendering at a constant 480x360.
    • AKA the fill-viewport state is always enabled.
    • This is the zero user-complexity approach that's also the most likely to upset users, since no one likes things being changed under their feet. So I'm not actually considering it, just pointing out that there's a cost to bringing in new complexity on a "meta" level of the project (not just how it behaves but how it's presented), too.
  4. We could not implement viewport filling at all.
    • AKA only have a fullscreen button.
    • This is boring but, strictly speaking, does avoid issues with making any complexity visible to the user. You either click full screen and fill your whole monitor, or don't and get a 480x360 stage. It's very easy to explain.
    • I think the cons (never being able to fine-tune the stage size just by adjusting your browser window / CodeSandbox iframe dimensions!) pretty heavily outweigh the avoided complexity here, though.

My inclinations are for options 2 (fullscreen button) and either 2 (configure in code, maybe default to always filling viewport) or 1 (fill viewport if significantly above 480x360).

I figure I'd share this before actually touching any of the code since there are a variety of options to choose from and there definitely might be other options I haven't even considered here. Please let me know what you think!

/cc everyone from #190: @sfederici, @PullJosh, @adroitwhiz

sfederici commented 6 months ago

My inclinations are for options 2 (fullscreen button)

I support this alternative

PullJosh commented 6 months ago

Feels to me like it would make sense to have provide the following options:

  1. By default the stage size is 480x360, but users have the option to change that. We should keep this option. But rather than strictly being visual pixels, I would think of it more as 480x360 "steps" in Scratch parlance. The project code operates on a grid of that size, but the project can be rendered smoothly at any size. (This is what we already have.)
  2. Options for the actual sizing of the stage on the screen.
    • One option should be a 1-to-1 match between steps and pixels. This is what we currently have. A 480x360 stage renders on a 480x360px canvas.
    • Another option could be to set the stage width and height to a number. For example, users might keep the "steps" grid size as 480x360 but change the canvas to be twice as big, 960x720px.
    • Finally, it probably makes sense to have some option that automatically changes the canvas size to match the space it has. Ideally I would want it to behave exactly as if you took a 480x360 canvas and set the CSS to canvas { width: 100%; } except to render smoothly. Under the hood, that means we probably need to measure the width of the parent container and then set the width/height of the canvas accordingly.
  3. Lastly, if you have a Project instance, it should be possible to call project.fullscreen() to invoke the fullscreen API. I don't have strong opinions about whether or not the generated output should include a button that does this.

For the sake of clarity, here's a rough proposed API:

// Change "steps" size of stage (what the Leopard code sees)
// We already have this option
const stage = new Stage({ width: 1920, height: 1080, costumeNumber: 1 });

// Change visual size of canvas – how large the project actually appears as pixels on your screen
const project = new Project(stage, sprites, {
  frameRate: 30,

  // A) Default canvas (dimensions match stage steps)
  canvas: "default",

  // B) Double size canvas
  canvas: { width: 960, height: 720 },

  // C) Viewport-filling canvas
  canvas: "full"
});

// Regardless of canvas size, you can always call the following to invoke legit fullscreen (including using a button or menu item in-game!)
project.fullscreen();
PullJosh commented 6 months ago

Oh, and I would not put a button in the generated HTML to toggle into fill-viewport mode. That seems like something you would configure in code (for example, if you were building a webpage with your project embedded in it).

towerofnix commented 6 months ago

I broadly agree @PullJosh, and appreciate the example API a lot! I'm a big fan of how that plays with embedding Leopard as part of a broader in-page context.

I'm mostly thinking about students who may not necessarily care about adding a fullscreen mode to their project, nor to set up specialized presentation for the viewport in their generated template page. My understanding is that these students aren't touching the HTML or CSS of the project at all; they care about making a game or program in Leopard, not making it look pretty online. Most of the time they view their project through a small CodeSandbox iframe or through a popout window (which may or may not itself be in fullscreen, manually zoomed in-and-blurry, etc).

A fullscreen button (note: not a fill-viewport button) feels appropriate for these students as they'd like a very specific, easy, and reliable way to just make the project fill up the whole screen, dang it! For the sake of presenting to a colleague, teacher, friend, etc. They have no interest in learning CSS on the fly, and they might not want to bother to add a button which runs project.fullscreen() in their actual project code. (Keep in mind that due to browser restrictions, it's never possible to use project.fullscreen() without a user interaction, e.g. having the game start out in fullscreen mode. So folk who want fullscreen would actually have to add a button to their game that sets it appropriately!)

I've come around though, and don't think we need any "magic" for the controlling the viewport — I totally agree with your suggestions there! It should be configured in code and have nice, easy to use settings, just like you've suggested. It's fundamentally the same decision-making as the frame rate for the project - collecting those settings together is intuitive and good.

Edit: I may have missed that you reacted 👍 to @sfederici's comment above, LOL.

PullJosh commented 6 months ago

@towerofnix Love it. I'm sold on the fullscreen button.

sfederici commented 6 months ago

My understanding is that these students aren't touching the HTML or CSS of the project at all; they care about making a game or program in Leopard, not making it look pretty online.

I agree. This is the most important thing for my students too