abstreet icon indicating copy to clipboard operation
abstreet copied to clipboard

LTN tutorial mode

Open dabreegster opened this issue 2 years ago • 20 comments

Madison designed an interactive tutorial mode for the LTN tool, but I haven't found time to implement it yet. If someone's not afraid of the slightly odd UI library A/B Street uses, it could be a fun medium-sized starter project. The design can be found at https://www.figma.com/file/HxSNM2146bcb0P0bNlW0x1/LTN-Working-Station-V2?node-id=5369%3A5464&t=cE5ZYUOuSsUMKwl2-0. Some samples: Screenshot from 2022-12-08 12-58-34 Screenshot from 2022-12-08 12-58-21 Embedding a second view of the map canvas is a bit advanced, but something we've figured out before. If anyone's interested in implementing, I can help flesh out smaller tasks and figure out how to structure the code.

CC @matthieu-foucault as an option I forgot from the other list

dabreegster avatar Dec 08 '22 12:12 dabreegster

Big :+1: to this such a great idea.

Robinlovelace avatar Dec 08 '22 14:12 Robinlovelace

If this is issue still needs someone, I would like to try my hands on this issue.

Cerabbite avatar Feb 21 '23 21:02 Cerabbite

Hi @Cerabbite, it's very much up for grabs, yes! I have not given thought to how to implement this yet. https://github.com/a-b-street/abstreet/blob/main/apps/ltn/src/design_ltn.rs handles the part of the UI that the tutorial covers. I would almost guess it could be easier to start a new module + State to do the tutorial, and pull in / refactor components to share. Another possible inspiration is https://github.com/a-b-street/abstreet/blob/main/apps/game/src/sandbox/gameplay/tutorial.rs, code for the traffic simulator's tutorial mode. This was written long ago and I would probably try things very differently today, though.

If you want any guidance or to talk through how to tackle this, I'm happy to help!

dabreegster avatar Feb 21 '23 22:02 dabreegster

@dabreegster do we want to initiate the tutorial from the command line just like we do with the other tutorial?

Cerabbite avatar Feb 22 '23 19:02 Cerabbite

It'd be useful, but not necessary. A button somewhere maybe in the current top bar of the tool, maybe?

On Wed, Feb 22, 2023, 7:18 PM Cerabbite @.***> wrote:

@dabreegster https://github.com/dabreegster do we want to initiate the tutorial from the command line just like we do with the other tutorial?

— Reply to this email directly, view it on GitHub https://github.com/a-b-street/abstreet/issues/1033#issuecomment-1440660574, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAMWLFZWW2ZTMJ4BSIP72YDWYZRA5ANCNFSM6AAAAAASYDQGSU . You are receiving this because you were mentioned.Message ID: @.***>

dabreegster avatar Feb 22 '23 19:02 dabreegster

image Is the tutorial supposed to automatically move on once the user has changed text size or is the only way to move the tutorial forward the "next" button?

Cerabbite avatar Feb 24 '23 22:02 Cerabbite

Hmm, good question. I think in this case, "next" should be the only way to advance. If we advanced when someone changed the text size, it'd be kind of annoying in many cases, because the default of 1 is probably fine. What do you think?

dabreegster avatar Feb 25 '23 18:02 dabreegster

I think that it can be useful to be able to interact with the components during the tutorial, however I agree with you that it can be annoying in some cases. One possibility could be to have a pop-up at the end of the tutorial to ask if they want to keep or discard all the changes they made during the tutorial. Although I don't know how hard that would be to implement.

Cerabbite avatar Feb 25 '23 20:02 Cerabbite

Offering to revert is a nice idea! It shouldn't be too tough to stash initial settings before the tutorial somewhere.

You might be finding that overlaying extra dialogs on top of the existing UI (and hiding / preventing interactions there) is tough, or the same with injecting something in the Design LTN mode that can figure out when the user has done something (and update hints / instructions / "good job!" panels accordingly). If you've done any other UI programming before or have any ideas about how it should work, I'd be very open to refactoring the LTN UI to make tutorial mode easier to implement. I've been separately working on Svelte web apps lately, and had a very experimental attempt to organize existing code into something more component-like.

dabreegster avatar Feb 25 '23 20:02 dabreegster

Yeah, working with the current UI is indeed quite difficult, and I still have not gotten used to it. However, the reorganization of the LTN code looks great I have not yet read all the code, but from what I have currently read it is all a lot clearer. I will try to build a simple MVP tutorial system on top of this refactored code to see how easy it is.

Cerabbite avatar Feb 25 '23 21:02 Cerabbite

The refactoring is very incomplete (it doesn't compile). I can try and work through finishing it if you like the direction it's taking.

In the meantime, I guess thinking about strategies for the tutorial... maybe it could just be a new state on top of DesignLTN. If you look at places using DrawBaselayer::PreviousState, there are ways to draw extra stuff on top of deactivated and greyed out things. But that might not make it easy to point to the correct part of the screen for parts of the tutorial instruction. So maybe adding in some kind of extra component in DesignLTN would be easier. Maybe we need a way to disable lots of the controls, before the user gets to the right part of the tutorial.

dabreegster avatar Feb 25 '23 21:02 dabreegster

I had not yet tried to compile, so thanks for letting me know.

Yeah, the main two things I was thinking about was drawing a gray (partially transparent) layer on top of the current screen state. To then draw the pop-up boxes on top of that, and to then either mask the part of the UI the user needs to access. Or redraw only that specific part back on top of the gray layer. One of the problems with this approach is that it does not disable keyboard shortcuts. So I do in fact think we need to disable all controls before going to the tutorial and then turning them back on when we need them.

Cerabbite avatar Feb 25 '23 21:02 Cerabbite

Is there anyway I can help in refactoring the UI code?

Cerabbite avatar Feb 26 '23 09:02 Cerabbite

drawing a gray (partially transparent) layer on top of the current screen state

map_gui::tools::grey_out_map or similar can help. Allowing clicks only in certain parts of the screen is an interesting idea I haven't explored much before; if that looks like a pattern we'll need, let me know if you need a guided tour through the widgetry guts.

So I do in fact think we need to disable all controls before going to the tutorial and then turning them back on when we need them.

Screenshot from 2023-02-26 13-11-36

Looking at Madison's tutorial design more closely... the first bit is non-interactive; looks like the controls aren't supposed to actually be active, just the prev/next/skip tour stuff. I think DrawBaselayer::PreviousState plus greying out parts of the screen should work here.

Screenshot from 2023-02-26 13-13-11

Looking at the rest, it seems like the Design LTN page and rest of the app is fully usable in the normal way; things aren't disabled. That suggests to me that we want to add some kind of optional tutorial controller state to DesignLTN struct and inject extra behavior there. That's roughly how the traffic sim tutorial worked; GameplayMode::Tutorial specializes some behavior in SandboxMode. I don't know if this is the nicest approach, curious to see what you find.

Is there anyway I can help in refactoring the UI code?

I merged #1068. If you have feedback or other ideas what to do, feel free to discuss those changes or make them. Getting a fresh pair of eyes on this is helpful, and you'll be butting heads with any problems with the current structure.

dabreegster avatar Feb 26 '23 13:02 dabreegster

After I pulled from the main branch to be up-to-date I ran into a strange bug, where the binary will compile, but then I get this screen image

setup widgetry...
setup app...
[2023-02-26T18:15:35Z ERROR map_model::make] 0 total intersections have some connectivity problem
[2023-02-26T18:15:35Z INFO  map_model::make::buildings] Discarded 1 buildings that weren't close enough to a sidewalk
[2023-02-26T18:15:35Z INFO  map_model::make::parking_lots] Discarded 0 parking lots that weren't close enough to a sidewalk
[2023-02-26T18:15:35Z INFO  map_model::pathfind::engine] Contraction hierarchy input graph has 2 nodes
[2023-02-26T18:15:35Z INFO  map_model::pathfind::engine] Contraction hierarchy input graph has 2 nodes
[2023-02-26T18:15:35Z INFO  map_model::pathfind::engine] Contraction hierarchy input graph has 4 nodes
[2023-02-26T18:15:35Z INFO  map_model::pathfind::engine] Contraction hierarchy input graph has 6 nodes
[2023-02-26T18:15:35Z INFO  map_gui::render::map] static DrawMap consumes 0 MB on the GPU
setup app took 0.1750s
setup widgetry... plus 0.2028s
setup widgetry took 0.3777s
[2023-02-26T18:15:35Z INFO  abstutil::time] - setup widgetry took 0.3777s
[2023-02-26T18:15:35Z INFO  abstutil::time]   - setup app took 0.1750s
[2023-02-26T18:15:35Z INFO  abstutil::time]   - ... plus 0.2028s
load data/system/us/seattle/maps/montlake.bin...
load data/system/us/seattle/maps/montlake.bin took 0.0086s
[2023-02-26T18:15:35Z INFO  abstutil::time] - load data/system/us/seattle/maps/montlake.bin took 0.0086s
[2023-02-26T18:15:35Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:36Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!
[2023-02-26T18:15:37Z WARN  widgetry::app_state] A state requested DrawBaselayer::PreviousState, but it's the only state on the stack!

And these warnings kept coming for a while. After this I went back to my own branches but it stayed there. Even after completely removing the abstreet folder from my computer and then cloning my own branch from before the reorganization suddenly had this same bug. Any idea on what this could be and what I can do about it?

Cerabbite avatar Feb 26 '23 18:02 Cerabbite

Ah, you need to download new binary map data. There's a binary incompatibility introduced recently. If cargo run --bin updater -- download doesn't fix, let me know

dabreegster avatar Feb 26 '23 20:02 dabreegster

Yep that fixed it thanks, i'll take a look at all the updates and new features of #1068 tomorrow

Cerabbite avatar Feb 26 '23 20:02 Cerabbite

if you need a guided tour through the widgetry guts.

I don't know if an entire tour is necessary, but getting to know some of the ins and outs of the widgetry might be useful

I think DrawBaselayer::PreviousState plus greying out parts of the screen should work here.

Thanks, I'll look into it.

i'll take a look at all the updates and new features of #1068 tomorrow

The code definitely looks a lot cleaner and easier to work with.

I added a CLI option for tutorial, so now you can use cargo run --bin ltn -- --tutorial true to start the tutorial (although the state of the tutorial is not there yet). Although it would be nice to just be able to run cargo run --bin ltn -- --tutorial normally, you would use an enum for that, but all the other CLI arguments are in a struct. Any suggestions?

Cerabbite avatar Feb 27 '23 22:02 Cerabbite

normally, you would use an enum for that, but all the other CLI arguments are in a struct.

I added tutorial: bool to Args in ltn/src/lib.rs and it seems to work fine for me

#[derive(StructOpt)]
struct Args {
    #[structopt(long)]
    tutorial: bool,
    /// Load a previously saved proposal with this name. Note this takes a name, not a full path.
    /// Or `remote/<ID>`.
    #[structopt(long)]
    proposal: Option<String>,
    /// Lock the user into one fixed neighbourhood, and remove many controls                         
    #[structopt(long)]
    consultation: Option<String>,
    #[structopt(flatten)]
    app_args: map_gui::SimpleAppArgs,
}

Looks like --tutorial=false still counts as true. I guess if structopt sees anything involving that flag, the value doesn't matter.

dabreegster avatar Feb 28 '23 09:02 dabreegster

I added tutorial: bool to Args in ltn/src/lib.rs and it seems to work fine for me

I used tutorial: Option<bool>, I think that that is why it did not work for me. Thanks

Cerabbite avatar Feb 28 '23 10:02 Cerabbite