Repository navigation
Prepare for per-window settings - #20328
Conversation
…n AppLogic on startup
…icrosoft/terminal into user/migrie/per-window-prequel
…icrosoft/terminal into user/migrie/per-window-prequel
Carlos Zamora (carlos-zamora)
left a comment
There was a problem hiding this comment.
Ok. I reviewed the PR and took a look at how it's used in part 2 (PR #20329) to get an idea of the feature being implemented.
Off the bat, the main design/architecture issues I have are...
- I don't like that
WindowSettingsis now everywhere. Though the changes are mechanical, it feels weird that everywhere in the code now has a responsibility of knowing what window we're in so that we can retrieve the right set of settings. I'd rather that that's only/mainly the responsibility of the settings model layer, so that each consumer just has to say "give me the value of this setting" rather than "give me the value in this window". I get why that's not the case, but I have an idea on how to fix it (more below). - I don't like that the settings are using
themesto apply them. Themes sound like they are intended to be purely visual. In MTSMSettings.h, I see thatMTSM_GLOBAL_ONLY_SETTINGSare settings that cannot differ across windows (good), but there's a good amount of settings inMTSM_WINDOW_SETTINGSthat don't make sense to me. For example,copyOnSelect,scrollToXare more interactivity-focused, so it would be weird to have two different windows have two different interaction behaviors. I also personally don't like that themes isn't in the settings UI yet, but that's a broader issue, so I feel weird about adding more functionality to an area that isn't exposed there yet. - (maybe more of a question for part 2) Should settings changed at runtime (i.e. "toggle always on top") be preserved? I think that would make sense.
I've been thinking about how these issues can be addressed. Some ideas:
- Replace
WindowSettings+GlobalAppSettingscoupling withGlobalAppSettingsinheritanceIInheritablealready does the kind of behavior I want: consumer calls a getter for a setting (i.e.AlwaysShowTabs()) and they just get the value directly without needing to think about the context. If we can use this, we could removeWindowSettingsDefaults()entirely because the value would just be resolved by the getter nice and clean.Profilealready has a similar system with profile defaults, so doing something like this would be more consistent.- One of the main issues right now is that
GlobalAppSettingscarries around_actionMap,_colorSchemes, and_themes. That said, I think we could just move them up toCascadiaSettingsand it shouldn't be too much trouble. Honestly, it would make more sense like that anyways.
- Directly allow global settings to be set in each "windows" entry
- I think this is more intuitive. Again, it follows the design of profiles and profile defaults, so it should be much easier to understand. Means that you can have
dockWindowbe a global setting too.
- I think this is more intuitive. Again, it follows the design of profiles and profile defaults, so it should be much easier to understand. Means that you can have
Hope this helps!
| { | ||
| // Create this only on the first time we load the settings. | ||
| _terminalSettingsCache = std::make_shared<TerminalSettingsCache>(settings); | ||
| _terminalSettingsCache = std::make_shared<TerminalSettingsCache>(settings, settings.WindowSettings(_WindowProperties.WindowName())); |
There was a problem hiding this comment.
| _terminalSettingsCache = std::make_shared<TerminalSettingsCache>(settings, settings.WindowSettings(_WindowProperties.WindowName())); | |
| _terminalSettingsCache = std::make_shared<TerminalSettingsCache>(settings, _currentWindowSettings()); |
nit: easier to read
This comment was marked as off-topic.
This comment was marked as off-topic.
|
RE: flipping things around, so that the settings model just gives you the right setting for the current window: Broadly, I don't think that's possible with our current settings model. Like the settings model is designed to be agnostic of the actual window instance that's hosting it. Like theoretically, we could make a separate settings window that's not attached to any terminal window. But most importantly, we only ever have one instance of the settings model. That one instance is shared across all terminal windows. How could we ever access this window's specific settings, without somehow passing in something to identify the window? re:
I didn't want to do that approach, because I felt like there were definitely settings that didn't make sense to be per-window. |
Carlos Zamora (carlos-zamora)
left a comment
There was a problem hiding this comment.
Discussed the IInheritable idea offline. We decided that the current approach is better.
Gave it all another full review. Mainly blocking over the one labelled 🛑.
| { | ||
| MainPage() = delete; | ||
| MainPage(const Model::CascadiaSettings& settings); | ||
| MainPage(const Model::CascadiaSettings& settings, const Model::WindowSettings& windowSettings); |
There was a problem hiding this comment.
🛑 Uh oh. What if you have two windows (light theme and dark theme), then you drag the settings tab from one window to the other? The theme definitely won't match up, and it looks like we'll be modifying the wrong WindowSettings.
Best fix might be to call UpdateSettings() when the tab/pane attaches to the new window. The only other option I can think of is passing around the window name instead, but I think we've mentioned that that's a bit fragile to renames.
There was a problem hiding this comment.
So, here's the sitch.
I very explicitly didn't try to handle the settings tab for multiple different window settings. The settings tab will always only let you edit the default window settings. There's not really any affordance within the settings tab itself to try to communicate which window you are editing the settings for.
I also felt like we could probably just p punt that decision for now, considering we're also actively working on redesigning huge parts of the settings.
There was a problem hiding this comment.
(Reviewing out loud, so feel free to skip to the end of this comment)
This is the part I'm still concerned about though. We're initializing the settings UI with _currentWindowSettings().
Then when we look at the MainPage ctor we have...
_windowSettingsSource{ windowSettings },
_windowSettingsClone{ _settingsClone.WindowSettingsDefaults() },
Looking through the TerminalSettingsEditor changes:
- MainPage
- MainPage::SettingsNav_Loaded: uses
_windowSettingsSource(aka current window settings) ✅ (means we use that for theming) - MainPage::ResetButton_Click(): uses
_windowSettingsSource✅ - profile view models:
_windowSettingsClone✅ _UpdateBackgroundForMica(): uses_windowSettingsSourcefor current theme ✅
- MainPage::SettingsNav_Loaded: uses
- Compatibility.h: uses
WindowSettingsDefaults()✅ - GlobalAppearanceViewModel.cpp: uses
_WindowSettings(aka_windowSettingsClone-->WindowSettingsDefaults()passed in above) ✅ - InteractionViewModel: uses
_WindowSettings(aka_windowSettingsClone-->WindowSettingsDefaults()passed in above) ✅ - LaunchViewModel: uses
WindowSettingsDefaults()✅ - NewTabMenuViewModel.cpp: uses
WindowSettingsDefaults()✅ - ProfileViewModel: uses
_windowSettings(aka_windowSettingsClone-->WindowSettingsDefaults()passed in above) ✅ - RenderingViewModel:
WindowSettingsDefaults✅- 📝 I noticed that this one doesn't modify the ctor. We just grab the
WindowSettingsDefaultsoff of_settings. Personally, I like this pattern more. The author doesn't even get the chance to use the wrong one.
- 📝 I noticed that this one doesn't modify the ctor. We just grab the
So yeah, looks like everything is good here. Long-term, I think matching the RenderingViewModel pattern would be best because it prevents people from using the wrong one and it makes it extra clear that we're accessing WindowSettingsDefaults. Especially since we're punting exposing different windows' settings. That's just my 2 cents tho.
Carlos Zamora (carlos-zamora)
left a comment
There was a problem hiding this comment.
Cool. Just blocking over the LogSettingChanges comment.
Re: #20328 (comment)
No longer blocking, though it would be nice to follow the RenderingViewModel pattern.
| _windowSettingsSource = windowSettings; | ||
| _windowSettingsClone = _settingsClone.WindowSettingsDefaults(); |
There was a problem hiding this comment.
nit: I recommend adding a brief comment here including what we talked about in #20328 (comment)
Reason is, I'm concerned somebody will come by later and see that this should be:
_windowSettingsSource = windowSettings;
_windowSettingsClone = windowSettings.Copy();
to match the _settingsSource/_settingsClone assignments above.
Based on the linked thread, we don't want anybody coming by and changing it like that. A comment would help prevent that from happening imo.
| else if (*clickedItemTag == interactionTag) | ||
| { | ||
| contentFrame().Navigate(xaml_typename<Editor::Interaction>(), winrt::make<NavigateToPageArgs>(winrt::make<InteractionViewModel>(_settingsClone.GlobalSettings()), *this, elementToFocus)); | ||
| contentFrame().Navigate(xaml_typename<Editor::Interaction>(), winrt::make<NavigateToPageArgs>(winrt::make<InteractionViewModel>(_settingsClone.GlobalSettings(), _settingsClone.WindowSettingsDefaults()), *this, elementToFocus)); |
There was a problem hiding this comment.
| contentFrame().Navigate(xaml_typename<Editor::Interaction>(), winrt::make<NavigateToPageArgs>(winrt::make<InteractionViewModel>(_settingsClone.GlobalSettings(), _settingsClone.WindowSettingsDefaults()), *this, elementToFocus)); | |
| contentFrame().Navigate(xaml_typename<Editor::Interaction>(), winrt::make<NavigateToPageArgs>(winrt::make<InteractionViewModel>(_settingsClone.GlobalSettings(), _windowSettingsClone), *this, elementToFocus)); |
nit: for consistency with the other branches in this function
| { | ||
| MainPage() = delete; | ||
| MainPage(const Model::CascadiaSettings& settings); | ||
| MainPage(const Model::CascadiaSettings& settings, const Model::WindowSettings& windowSettings); |
There was a problem hiding this comment.
(Reviewing out loud, so feel free to skip to the end of this comment)
This is the part I'm still concerned about though. We're initializing the settings UI with _currentWindowSettings().
Then when we look at the MainPage ctor we have...
_windowSettingsSource{ windowSettings },
_windowSettingsClone{ _settingsClone.WindowSettingsDefaults() },
Looking through the TerminalSettingsEditor changes:
- MainPage
- MainPage::SettingsNav_Loaded: uses
_windowSettingsSource(aka current window settings) ✅ (means we use that for theming) - MainPage::ResetButton_Click(): uses
_windowSettingsSource✅ - profile view models:
_windowSettingsClone✅ _UpdateBackgroundForMica(): uses_windowSettingsSourcefor current theme ✅
- MainPage::SettingsNav_Loaded: uses
- Compatibility.h: uses
WindowSettingsDefaults()✅ - GlobalAppearanceViewModel.cpp: uses
_WindowSettings(aka_windowSettingsClone-->WindowSettingsDefaults()passed in above) ✅ - InteractionViewModel: uses
_WindowSettings(aka_windowSettingsClone-->WindowSettingsDefaults()passed in above) ✅ - LaunchViewModel: uses
WindowSettingsDefaults()✅ - NewTabMenuViewModel.cpp: uses
WindowSettingsDefaults()✅ - ProfileViewModel: uses
_windowSettings(aka_windowSettingsClone-->WindowSettingsDefaults()passed in above) ✅ - RenderingViewModel:
WindowSettingsDefaults✅- 📝 I noticed that this one doesn't modify the ctor. We just grab the
WindowSettingsDefaultsoff of_settings. Personally, I like this pattern more. The author doesn't even get the chance to use the wrong one.
- 📝 I noticed that this one doesn't modify the ctor. We just grab the
So yeah, looks like everything is good here. Long-term, I think matching the RenderingViewModel pattern would be best because it prevents people from using the wrong one and it makes it extra clear that we're accessing WindowSettingsDefaults. Especially since we're punting exposing different windows' settings. That's just my 2 cents tho.
There was a problem hiding this comment.
🛑 Noticed one thing worth flagging. Looks like window settings aren't hooked up to LogSettingChanges(), so we won't be tracking usage for those global settings anymore. That should probably be fixed.
Carlos Zamora (carlos-zamora)
left a comment
There was a problem hiding this comment.
Thanks for bearing with me. Nice work :)
Dustin L. Howett (DHowett)
left a comment
There was a problem hiding this comment.
Looks good to me. Open questions which I shall hope for you to answer before merging:
- did we miss updating the window settings object when we rename a window? what happens if you rename a window to a different window settings group anyway...
- that's all
good work. i love how you've staged this
| // Stash away the current requested theme of the app. We'll need that in | ||
| // _BackgroundBrush() to do a theme-aware resource lookup | ||
| _requestedTheme = settings.GlobalSettings().CurrentTheme().RequestedTheme(); | ||
| _requestedTheme = settings.GlobalSettings().CurrentTheme(windowSettings).RequestedTheme(); |
There was a problem hiding this comment.
curious why this one is not on WindowSettings
There was a problem hiding this comment.
i talked myself into a (rather unsatisfying but acceptable) answer
| _TextMeasurement = windowSettings.TextMeasurement(); | ||
| _AmbiguousWidth = windowSettings.AmbiguousWidth(); |
There was a problem hiding this comment.
these two are actually application-level toggles that we can't make per-window; what should we do with them?
There was a problem hiding this comment.
they impact the CodepointWidthDetector singleton
| } | ||
|
|
||
| winrt::Microsoft::Terminal::Settings::Model::Theme GlobalAppSettings::CurrentTheme() noexcept | ||
| winrt::Microsoft::Terminal::Settings::Model::Theme GlobalAppSettings::CurrentTheme(const Model::WindowSettings& window) noexcept |
There was a problem hiding this comment.
I read all the way here and I still don't understand why this is here. Is it because _themes is a member of GlobalAppSettings and it's the only way to access it?
- Merge upstream main (per-window settings refactor microsoft#20328 and 24 others) - Resolve TabPosition migration: global -> WindowSettings (_currentWindowSettings()) - Fix CurrentTheme() calls to new signature CurrentTheme(windowSettings)
|
Sad news: Test harness broke. |
|
asldkfjalskdfjasldkf Okay I fixed the local tests but I'll take a look at the PGO ones. Sorry :/ |
Regressed in #20328.
This is part one of a two-part pair of PRs to add support for per window name settings to the Windows terminal.
This first PR functionally does very little. It introduces the concept of a
WindowSettingsobject, where eachWindowSettingsobject may be different based off of the name of the window. However, the settings model doesn't actually expose different instances ofWindowSettingscurrently. It just introduces one fake instance that actually just wraps the global settings object to store all of them. This means that functionally the code hasn't actually changed in this PR. We are just accessing these properties through an intermediate type.Largely this PR is mostly mechanical. where we previously would have been accessing properties on the
GlobalAppSettings, now we are just accessing the_currentWindowSettings()(via a helper, or passing as a parameter).All of this refactoring can be done now to always access the correct window settings object so that the next PR is much easier to read. That is where it will get more interesting and we will have more instances of window settings.
re: #9992