a-b-street / abstreet

Transportation planning and traffic simulation software for creating cities friendlier to walking, biking, and public transit
https://a-b-street.github.io/docs/
Apache License 2.0
7.75k stars 345 forks source link

LTN tutorial mode #1033

Open dabreegster opened 1 year ago

dabreegster commented 1 year ago

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

Robinlovelace commented 1 year ago

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

Cerabbite commented 1 year ago

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

dabreegster commented 1 year ago

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!

Cerabbite commented 1 year ago

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

dabreegster commented 1 year ago

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: @.***>

Cerabbite commented 1 year ago

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?

dabreegster commented 1 year ago

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?

Cerabbite commented 1 year ago

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.

dabreegster commented 1 year ago

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.

Cerabbite commented 1 year ago

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.

dabreegster commented 1 year ago

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.

Cerabbite commented 1 year ago

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 commented 1 year ago

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

dabreegster commented 1 year ago

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.

Cerabbite commented 1 year ago

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?

dabreegster commented 1 year ago

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

Cerabbite commented 1 year ago

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

Cerabbite commented 1 year ago

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?

dabreegster commented 1 year ago

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.

Cerabbite commented 1 year ago

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