diff --git a/.github/actions/spelling/README.md b/.github/actions/spelling/README.md new file mode 100644 index 00000000000..4c40f7f02ac --- /dev/null +++ b/.github/actions/spelling/README.md @@ -0,0 +1,15 @@ +# check-spelling/check-spelling configuration + +File | Purpose | Format | Info +-|-|-|- +[allow/*.txt](allow/) | Add words to the dictionary | one word per line (only letters and `'`s allowed) | [allow](https://github.com/check-spelling/check-spelling/wiki/Configuration#allow) +[reject.txt](reject.txt) | Remove words from the dictionary (after allow) | grep pattern matching whole dictionary words | [reject](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-reject) +[excludes.txt](excludes.txt) | Files to ignore entirely | perl regular expression | [excludes](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-excludes) +[patterns/*.txt](patterns/) | Patterns to ignore from checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns) +[candidate.patterns](candidate.patterns) | Patterns that might be worth adding to [patterns.txt](patterns.txt) | perl regular expression with optional comment block introductions (all matches will be suggested) | [candidates](https://github.com/check-spelling/check-spelling/wiki/Feature:-Suggest-patterns) +[line_forbidden.patterns](line_forbidden.patterns) | Patterns to flag in checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns) +[expect/*.txt](expect.txt) | Expected words that aren't in the dictionary | one word per line (sorted, alphabetically) | [expect](https://github.com/check-spelling/check-spelling/wiki/Configuration#expect) +[advice.md](advice.md) | Supplement for GitHub comment when unrecognized words are found | GitHub Markdown | [advice](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice) + +Note: you can replace any of these files with a directory by the same name (minus the suffix) +and then include multiple files inside that directory (with that suffix) to merge multiple files together. diff --git a/.github/actions/spelling/advice.md b/.github/actions/spelling/advice.md index 885b1a6978d..d82df49ee22 100644 --- a/.github/actions/spelling/advice.md +++ b/.github/actions/spelling/advice.md @@ -1,4 +1,4 @@ - +
:pencil2: Contributor please read this @@ -6,7 +6,7 @@ By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later. -:warning: The command is written for posix shells. You can copy the contents of each `perl` command excluding the outer `'` marks and dropping any `'"`/`"'` quotation mark pairs into a file and then run `perl file.pl` from the root of the repository to run the code. Alternatively, you can manually insert the items... +:warning: The command is written for posix shells. If it doesn't work for you, you can manually _add_ (one word per line) / _remove_ items to `expect.txt` and the `excludes.txt` files. If the listed items are: @@ -20,31 +20,29 @@ See the `README.md` in each directory for more information. :microscope: You can test your commits **without** *appending* to a PR by creating a new branch with that extra change and pushing it to your fork. The [check-spelling](https://github.com/marketplace/actions/check-spelling) action will run in response to your **push** -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. :wink: -
:clamp: If you see a bunch of garbage -If it relates to a ... -
well-formed pattern +
If the flagged items are :exploding_head: false positives -See if there's a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it. +If items relate to a ... +* binary file (or some other file you wouldn't want to check at all). -If not, try writing one and adding it to a `patterns/{file}.txt`. + Please add a file path to the `excludes.txt` file matching the containing file. -Patterns are Perl 5 Regular Expressions - you can [test]( -https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines. + File paths are Perl 5 Regular Expressions - you can [test]( +https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files. -Note that patterns can't match multiline strings. -
-
binary-ish string + `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md]( +../tree/HEAD/README.md) (on whichever branch you're using). -Please add a file path to the `excludes.txt` file instead of just accepting the garbage. +* well-formed pattern. -File paths are Perl 5 Regular Expressions - you can [test]( -https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files. + If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it, + try adding it to the `patterns.txt` file. -`^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md]( -../tree/HEAD/README.md) (on whichever branch you're using). -
+ Patterns are Perl 5 Regular Expressions - you can [test]( +https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines. + Note that patterns can't match multiline strings.
diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index 3452bf4a8d6..eaa0a471190 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -1,7 +1,18 @@ +admins +allcolors Apc apc +breadcrumb +breadcrumbs +bsd +calt +ccmp +changelog clickable +clig +CMMI copyable +cybersecurity dalet Dcs dcs @@ -11,43 +22,87 @@ downside downsides dze dzhe +EDDB +EDDC Enum'd +Fitt +formattings +FTCS ftp +fvar +gantt +gcc geeksforgeeks ghe +github gje +godbolt hostname hostnames +https hyperlink hyperlinking hyperlinks +iconify img +inlined It'd kje +libfuzzer +libuv +liga lje +Llast +llvm +Lmid +locl +lol +lorem +Lorigin maxed +minimalistic +mkmk +mnt mru nje +noreply ogonek ok'd overlined +pipeline postmodern ptys qof +qps +rclt reimplementation reserialization reserialize reserializes +rlig runtimes shcha +slnt Sos +ssh +timeline +timelines timestamped +TLDR tokenizes tonos +toolset tshe +ubuntu +uiatextrange UIs +und +unregister versioned +vsdevcmd We'd wildcards +XBox +YBox yeru zhe diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 8252b10b79a..e0cc7550095 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -1,28 +1,48 @@ ACCEPTFILES ACCESSDENIED +acl +aclapi alignas alignof +APPLYTOSUBMENUS +appxrecipe bitfield bitfields +BUILDBRANCH +BUILDMSG BUILDNUMBER +BYCOMMAND +BYPOSITION charconv CLASSNOTAVAILABLE +CLOSEAPP cmdletbinding COLORPROPERTY colspan COMDLG +commandlinetoargv +comparand cstdint CXICON CYICON +Dacl dataobject dcomp DERR dlldata +DNE DONTADDTORECENT +DWMSBT +DWMWA +DWMWA DWORDLONG +endfor +ENDSESSION +enumset environstrings EXPCMDFLAGS EXPCMDSTATE +filetime FILTERSPEC FORCEFILESYSTEM FORCEMINIMIZE @@ -31,12 +51,16 @@ fullkbd futex GETDESKWALLPAPER GETHIGHCONTRAST +GETMOUSEHOVERTIME Hashtable HIGHCONTRASTON HIGHCONTRASTW hotkeys href hrgn +HTCLOSE +hwinsta +HWINSTA IActivation IApp IAppearance @@ -45,6 +69,7 @@ IBind IBox IClass IComparable +IComparer IConnection ICustom IDialog @@ -52,90 +77,157 @@ IDirect IExplorer IFACEMETHOD IFile +IGraphics IInheritable IMap +IMonarch IObject iosfwd IPackage IPeasant +ISetup +isspace IStorage istream IStringable ITab ITaskbar +itow IUri IVirtual +KEYSELECT LCID llabs llu localtime lround +Lsa +lsass LSHIFT +LTGRAY +MAINWINDOW +memchr +memicmp +MENUCOMMAND +MENUDATA +MENUINFO +MENUITEMINFOW +mmeapi +MOUSELEAVE +mov +mptt msappx MULTIPLEUSE NCHITTEST NCLBUTTONDBLCLK +NCMOUSELEAVE +NCMOUSEMOVE NCRBUTTONDBLCLK +NIF +NIN NOAGGREGATION NOASYNC NOCHANGEDIR NOPROGRESS NOREDIRECTIONBITMAP NOREPEAT +NOTIFYBYPOS +NOTIFYICON +NOTIFYICONDATA ntprivapi oaidl ocidl +ODR +offsetof +ofstream +onefuzz osver OSVERSIONINFOEXW otms OUTLINETEXTMETRICW overridable +PACL PAGESCROLL +PATINVERT +PEXPLICIT PICKFOLDERS pmr +ptstr +QUERYENDSESSION +rcx REGCLS RETURNCMD rfind +ROOTOWNER roundf RSHIFT +SACL schandle semver serializer +SETVERSION SHELLEXECUTEINFOW shobjidl +SHOWHIDE SHOWMINIMIZED +SHOWTIP SINGLEUSE SIZENS smoothstep snprintf spsc sregex +SRWLOC +SRWLOCK STDCPP STDMETHOD strchr +strcpy streambuf +strtoul Stubless Subheader Subpage syscall +SYSTEMBACKDROP +TABROW +TASKBARCREATED TBPF THEMECHANGED +tlg +TME tmp +tmpdir tolower +toupper +TRACKMOUSEEVENT TTask TVal +UChar +UFIELD +ULARGE +UOI UPDATEINIFILE userenv +USEROBJECTFLAGS +Viewbox +virtualalloc wcsstr wcstoui winmain +winsta +winstamin +wmemcmp wpc +WSF wsregex wwinmain +xchg XDocument XElement xfacet xhash +XIcon xiosbase xlocale xlocbuf @@ -144,9 +236,13 @@ xlocmes xlocmon xlocnum xloctime +XMax xmemory XParse +xpath xstddef xstring xtree xutility +YIcon +YMax diff --git a/.github/actions/spelling/allow/fonts.txt b/.github/actions/spelling/allow/fonts.txt index 0af346b0161..c9931b2fc07 100644 --- a/.github/actions/spelling/allow/fonts.txt +++ b/.github/actions/spelling/allow/fonts.txt @@ -7,3 +7,4 @@ Iosevka MDL Monofur Segoe +wght diff --git a/.github/actions/spelling/allow/math.txt b/.github/actions/spelling/allow/math.txt index 1482aedaba0..bf8960f0089 100644 --- a/.github/actions/spelling/allow/math.txt +++ b/.github/actions/spelling/allow/math.txt @@ -1,3 +1,11 @@ +atan +CPrime +HBar +HPrime isnan +LPrime +LStep powf +RSub sqrtf +ULP diff --git a/.github/actions/spelling/allow/microsoft.txt b/.github/actions/spelling/allow/microsoft.txt index b8187ab507a..1f4a28664f2 100644 --- a/.github/actions/spelling/allow/microsoft.txt +++ b/.github/actions/spelling/allow/microsoft.txt @@ -1,5 +1,6 @@ ACLs ADMINS +advapi altform altforms appendwttlogging @@ -15,8 +16,10 @@ CPLs cpptools cppvsdbg CPRs +cryptbase DACL DACLs +defaultlib diffs disposables dotnetfeed @@ -24,14 +27,24 @@ DTDs DWINRT enablewttlogging Intelli +IVisual +libucrt +libucrtd LKG +LOCKFILE +Lxss mfcribbon microsoft microsoftonline +MSAA msixbundle +MSVC +MSVCP muxc netcore +Onefuzz osgvsowi +PFILETIME pgc pgo pgosweep @@ -39,10 +52,14 @@ powerrename powershell propkey pscustomobject +QWORD +regedit robocopy SACLs +sdkddkver Shobjidl Skype +SRW sxs Sysinternals sysnative @@ -50,6 +67,8 @@ systemroot taskkill tasklist tdbuildteamid +ucrt +ucrtd unvirtualized VCRT vcruntime @@ -57,6 +76,7 @@ Virtualization visualstudio vscode VSTHRD +winsdkver wlk wslpath wtl diff --git a/.github/actions/spelling/allow/names.txt b/.github/actions/spelling/allow/names.txt index b4fe3abaea5..1c6ef9a373c 100644 --- a/.github/actions/spelling/allow/names.txt +++ b/.github/actions/spelling/allow/names.txt @@ -1,14 +1,18 @@ Anup austdi +arkthur Ballmer bhoj Bhojwani +Bluloco carlos dhowett Diviness dsafa duhowett +DXP ekg +eryksun ethanschoonover Firefox Gatta @@ -20,6 +24,7 @@ Hernan Howett Illhardt iquilezles +italo jantari jerrysh Kaiyu @@ -31,8 +36,11 @@ Kourosh kowalczyk leonmsft Lepilleur +lhecker lukesampson +Macbook Manandhar +masserano mbadolato Mehrain menger @@ -52,6 +60,7 @@ oldnewthing opengl osgwiki pabhojwa +panos paulcam pauldotknopf PGP @@ -60,12 +69,17 @@ Rincewind rprichard Schoonover shadertoy +Shomnipotence +simioni Somuah sonph sonpham stakx +talo thereses Walisch +WDX +Wellons Wirt Wojciech zadjii diff --git a/.github/actions/spelling/candidate.patterns b/.github/actions/spelling/candidate.patterns new file mode 100644 index 00000000000..4b40e728ee3 --- /dev/null +++ b/.github/actions/spelling/candidate.patterns @@ -0,0 +1,523 @@ +# marker to ignore all code on line +^.*/\* #no-spell-check-line \*/.*$ +# marker for ignoring a comment to the end of the line +// #no-spell-check.*$ + +# patch hunk comments +^\@\@ -\d+(?:,\d+|) \+\d+(?:,\d+|) \@\@ .* +# git index header +index [0-9a-z]{7,40}\.\.[0-9a-z]{7,40} + +# cid urls +(['"])cid:.*?\g{-1} + +# data url in parens +\(data:[^)]*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\) +# data url in quotes +([`'"])data:.*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1} +# data url +data:[-a-zA-Z=;:/0-9+]*,\S* + +# mailto urls +mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} + +# magnet urls +magnet:[?=:\w]+ + +# magnet urls +"magnet:[^"]+" + +# obs: +"obs:[^"]*" + +# The `\b` here means a break, it's the fancy way to handle urls, but it makes things harder to read +# In this examples content, I'm using a number of different ways to match things to show various approaches +# asciinema +\basciinema\.org/a/[0-9a-zA-Z]+ + +# apple +\bdeveloper\.apple\.com/[-\w?=/]+ +# Apple music +\bembed\.music\.apple\.com/fr/playlist/usr-share/[-\w.]+ + +# appveyor api +\bci\.appveyor\.com/api/projects/status/[0-9a-z]+ +# appveyor project +\bci\.appveyor\.com/project/(?:[^/\s"]*/){2}builds?/\d+/job/[0-9a-z]+ + +# Amazon + +# Amazon +\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|) +# AWS S3 +\b\w*\.s3[^.]*\.amazonaws\.com/[-\w/&#%_?:=]* +# AWS execute-api +\b[0-9a-z]{10}\.execute-api\.[-0-9a-z]+\.amazonaws\.com\b +# AWS ELB +\b\w+\.[-0-9a-z]+\.elb\.amazonaws\.com\b +# AWS SNS +\bsns\.[-0-9a-z]+.amazonaws\.com/[-\w/&#%_?:=]* +# AWS VPC +vpc-\w+ + +# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there +# YouTube url +\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]* +# YouTube music +\bmusic\.youtube\.com/youtubei/v1/browse(?:[?&]\w+=[-a-zA-Z0-9?&=_]*) +# YouTube tag +<\s*youtube\s+id=['"][-a-zA-Z0-9?_]*['"] +# YouTube image +\bimg\.youtube\.com/vi/[-a-zA-Z0-9?&=_]* +# Google Accounts +\baccounts.google.com/[-_/?=.:;+%&0-9a-zA-Z]* +# Google Analytics +\bgoogle-analytics\.com/collect.[-0-9a-zA-Z?%=&_.~]* +# Google APIs +\bgoogleapis\.(?:com|dev)/[a-z]+/(?:v\d+/|)[a-z]+/[-@:./?=\w+|&]+ +# Google Storage +\b[-a-zA-Z0-9.]*\bstorage\d*\.googleapis\.com(?:/\S*|) +# Google Calendar +\bcalendar\.google\.com/calendar(?:/u/\d+|)/embed\?src=[@./?=\w&%]+ +\w+\@group\.calendar\.google\.com\b +# Google DataStudio +\bdatastudio\.google\.com/(?:(?:c/|)u/\d+/|)(?:embed/|)(?:open|reporting|datasources|s)/[-0-9a-zA-Z]+(?:/page/[-0-9a-zA-Z]+|) +# The leading `/` here is as opposed to the `\b` above +# ... a short way to match `https://` or `http://` since most urls have one of those prefixes +# Google Docs +/docs\.google\.com/[a-z]+/(?:ccc\?key=\w+|(?:u/\d+|d/(?:e/|)[0-9a-zA-Z_-]+/)?(?:edit\?[-\w=#.]*|/\?[\w=&]*|)) +# Google Drive +\bdrive\.google\.com/(?:file/d/|open)[-0-9a-zA-Z_?=]* +# Google Groups +\bgroups\.google\.com/(?:(?:forum/#!|d/)(?:msg|topics?|searchin)|a)/[^/\s"]+/[-a-zA-Z0-9$]+(?:/[-a-zA-Z0-9]+)* +# Google Maps +\bmaps\.google\.com/maps\?[\w&;=]* +# Google themes +themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+. +# Google CDN +\bclients2\.google(?:usercontent|)\.com[-0-9a-zA-Z/.]* +# Goo.gl +/goo\.gl/[a-zA-Z0-9]+ +# Google Chrome Store +\bchrome\.google\.com/webstore/detail/[-\w]*(?:/\w*|) +# Google Books +\bgoogle\.(?:\w{2,4})/books(?:/\w+)*\?[-\w\d=&#.]* +# Google Fonts +\bfonts\.(?:googleapis|gstatic)\.com/[-/?=:;+&0-9a-zA-Z]* +# Google Forms +\bforms\.gle/\w+ +# Google Scholar +\bscholar\.google\.com/citations\?user=[A-Za-z0-9_]+ +# Google Colab Research Drive +\bcolab\.research\.google\.com/drive/[-0-9a-zA-Z_?=]* + +# GitHub SHAs (api) +\bapi.github\.com/repos(?:/[^/\s"]+){3}/[0-9a-f]+\b +# GitHub SHAs (markdown) +(?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|) +# GitHub SHAs +\bgithub\.com(?:/[^/\s"]+){2}[@#][0-9a-f]+\b +# GitHub wiki +\bgithub\.com/(?:[^/]+/){2}wiki/(?:(?:[^/]+/|)_history|[^/]+(?:/_compare|)/[0-9a-f.]{40,})\b +# githubusercontent +/[-a-z0-9]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]* +# githubassets +\bgithubassets.com/[0-9a-f]+(?:[-/\w.]+) +# gist github +\bgist\.github\.com/[^/\s"]+/[0-9a-f]+ +# git.io +\bgit\.io/[0-9a-zA-Z]+ +# GitHub JSON +"node_id": "[-a-zA-Z=;:/0-9+]*" +# Contributor +\[[^\]]+\]\(https://github\.com/[^/\s"]+\) +# GHSA +GHSA(?:-[0-9a-z]{4}){3} + +# GitLab commit +\bgitlab\.[^/\s"]*/\S+/\S+/commit/[0-9a-f]{7,16}#[0-9a-f]{40}\b +# GitLab merge requests +\bgitlab\.[^/\s"]*/\S+/\S+/-/merge_requests/\d+/diffs#[0-9a-f]{40}\b +# GitLab uploads +\bgitlab\.[^/\s"]*/uploads/[-a-zA-Z=;:/0-9+]* +# GitLab commits +\bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b + +# binanace +accounts.binance.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]* + +# bitbucket diff +\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}diff(?:stat|)(?:/[^/\s"]+){2}:[0-9a-f]+ +# bitbucket repositories commits +\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}commits?/[0-9a-f]+ +# bitbucket commits +\bbitbucket\.org/(?:[^/\s"]+/){2}commits?/[0-9a-f]+ + +# bit.ly +\bbit\.ly/\w+ + +# bitrise +\bapp\.bitrise\.io/app/[0-9a-f]*/[\w.?=&]* + +# bootstrapcdn.com +\bbootstrapcdn\.com/[-./\w]+ + +# cdn.cloudflare.com +\bcdnjs\.cloudflare\.com/[./\w]+ + +# circleci +\bcircleci\.com/gh(?:/[^/\s"]+){1,5}.[a-z]+\?[-0-9a-zA-Z=&]+ + +# gitter +\bgitter\.im(?:/[^/\s"]+){2}\?at=[0-9a-f]+ + +# gravatar +\bgravatar\.com/avatar/[0-9a-f]+ + +# ibm +[a-z.]*ibm\.com/[-_#=:%!?~.\\/\d\w]* + +# imgur +\bimgur\.com/[^.]+ + +# Internet Archive +\barchive\.org/web/\d+/(?:[-\w.?,'/\\+&%$#_:]*) + +# discord +/discord(?:app\.com|\.gg)/(?:invite/)?[a-zA-Z0-9]{7,} + +# Disqus +\bdisqus\.com/[-\w/%.()!?&=_]* + +# medium link +\blink\.medium\.com/[a-zA-Z0-9]+ +# medium +\bmedium\.com/\@?[^/\s"]+/[-\w]+ + +# microsoft +\b(?:https?://|)(?:(?:download\.visualstudio|docs|msdn2?|research)\.microsoft|blogs\.msdn)\.com/[-_a-zA-Z0-9()=./%]* +# powerbi +\bapp\.powerbi\.com/reportEmbed/[^"' ]* +# vs devops +\bvisualstudio.com(?::443|)/[-\w/?=%&.]* +# microsoft store +\bmicrosoft\.com/store/apps/\w+ + +# mvnrepository.com +\bmvnrepository\.com/[-0-9a-z./]+ + +# now.sh +/[0-9a-z-.]+\.now\.sh\b + +# oracle +\bdocs\.oracle\.com/[-0-9a-zA-Z./_?#&=]* + +# chromatic.com +/\S+.chromatic.com\S*[")] + +# codacy +\bapi\.codacy\.com/project/badge/Grade/[0-9a-f]+ + +# compai +\bcompai\.pub/v1/png/[0-9a-f]+ + +# mailgun api +\.api\.mailgun\.net/v3/domains/[0-9a-z]+\.mailgun.org/messages/[0-9a-zA-Z=@]* +# mailgun +\b[0-9a-z]+.mailgun.org + +# /message-id/ +/message-id/[-\w@./%]+ + +# Reddit +\breddit\.com/r/[/\w_]* + +# requestb.in +\brequestb\.in/[0-9a-z]+ + +# sched +\b[a-z0-9]+\.sched\.com\b + +# Slack url +slack://[a-zA-Z0-9?&=]+ +# Slack +\bslack\.com/[-0-9a-zA-Z/_~?&=.]* +# Slack edge +\bslack-edge\.com/[-a-zA-Z0-9?&=%./]+ +# Slack images +\bslack-imgs\.com/[-a-zA-Z0-9?&=%.]+ + +# shields.io +\bshields\.io/[-\w/%?=&.:+;,]* + +# stackexchange -- https://stackexchange.com/feeds/sites +\b(?:askubuntu|serverfault|stack(?:exchange|overflow)|superuser).com/(?:questions/\w+/[-\w]+|a/) + +# Sentry +[0-9a-f]{32}\@o\d+\.ingest\.sentry\.io\b + +# Twitter markdown +\[\@[^[/\]:]*?\]\(https://twitter.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)\) +# Twitter hashtag +\btwitter\.com/hashtag/[\w?_=&]* +# Twitter status +\btwitter\.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|) +# Twitter profile images +\btwimg\.com/profile_images/[_\w./]* +# Twitter media +\btwimg\.com/media/[-_\w./?=]* +# Twitter link shortened +\bt\.co/\w+ + +# facebook +\bfburl\.com/[0-9a-z_]+ +# facebook CDN +\bfbcdn\.net/[\w/.,]* +# facebook watch +\bfb\.watch/[0-9A-Za-z]+ + +# dropbox +\bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+ + +# ipfs protocol +ipfs://[0-9a-z]* +# ipfs url +/ipfs/[0-9a-z]* + +# w3 +\bw3\.org/[-0-9a-zA-Z/#.]+ + +# loom +\bloom\.com/embed/[0-9a-f]+ + +# regex101 +\bregex101\.com/r/[^/\s"]+/\d+ + +# figma +\bfigma\.com/file(?:/[0-9a-zA-Z]+/)+ + +# freecodecamp.org +\bfreecodecamp\.org/[-\w/.]+ + +# image.tmdb.org +\bimage\.tmdb\.org/[/\w.]+ + +# mermaid +\bmermaid\.ink/img/[-\w]+|\bmermaid-js\.github\.io/mermaid-live-editor/#/edit/[-\w]+ + +# Wikipedia +\ben\.wikipedia\.org/wiki/[-\w%.#]+ + +# gitweb +[^"\s]+/gitweb/\S+;h=[0-9a-f]+ + +# HyperKitty lists +/archives/list/[^@/]+\@[^/\s"]*/message/[^/\s"]*/ + +# lists +/thread\.html/[^"\s]+ + +# list-management +\blist-manage\.com/subscribe(?:[?&](?:u|id)=[0-9a-f]+)+ + +# kubectl.kubernetes.io/last-applied-configuration +"kubectl.kubernetes.io/last-applied-configuration": ".*" + +# pgp +\bgnupg\.net/pks/lookup[?&=0-9a-zA-Z]* + +# Spotify +\bopen\.spotify\.com/embed/playlist/\w+ + +# Mastodon +\bmastodon\.[-a-z.]*/(?:media/|\@)[?&=0-9a-zA-Z_]* + +# scastie +\bscastie\.scala-lang\.org/[^/]+/\w+ + +# images.unsplash.com +\bimages\.unsplash\.com/(?:(?:flagged|reserve)/|)[-\w./%?=%&.;]+ + +# pastebin +\bpastebin\.com/[\w/]+ + +# heroku +\b\w+\.heroku\.com/source/archive/\w+ + +# quip +\b\w+\.quip\.com/\w+(?:(?:#|/issues/)\w+)? + +# badgen.net +\bbadgen\.net/badge/[^")\]'\s]+ + +# statuspage.io +\w+\.statuspage\.io\b + +# media.giphy.com +\bmedia\.giphy\.com/media/[^/]+/[\w.?&=]+ + +# tinyurl +\btinyurl\.com/\w+ + +# getopts +\bgetopts\s+(?:"[^"]+"|'[^']+') + +# ANSI color codes +(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m + +# URL escaped characters +\%[0-9A-F][A-F] +# IPv6 +\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b +# c99 hex digits (not the full format, just one I've seen) +0x[0-9a-fA-F](?:\.[0-9a-fA-F]*|)[pP] +# Punycode +\bxn--[-0-9a-z]+ +# sha +sha\d+:[0-9]*[a-f]{3,}[0-9a-f]* +# sha-... -- uses a fancy capture +(['"]|")[0-9a-f]{40,}\g{-1} +# hex runs +\b[0-9a-fA-F]{16,}\b +# hex in url queries +=[0-9a-fA-F]*?(?:[A-F]{3,}|[a-f]{3,})[0-9a-fA-F]*?& +# ssh +(?:ssh-\S+|-nistp256) [-a-zA-Z=;:/0-9+]{12,} + +# PGP +\b(?:[0-9A-F]{4} ){9}[0-9A-F]{4}\b +# GPG keys +\b(?:[0-9A-F]{4} ){5}(?: [0-9A-F]{4}){5}\b +# Well known gpg keys +.well-known/openpgpkey/[\w./]+ + +# uuid: +\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b +# hex digits including css/html color classes: +(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b +# integrity +integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}" + +# https://www.gnu.org/software/groff/manual/groff.html +# man troff content +\\f[BCIPR] +# ' +\\\(aq + +# .desktop mime types +^MimeTypes?=.*$ +# .desktop localized entries +^[A-Z][a-z]+\[[a-z]+\]=.*$ +# Localized .desktop content +Name\[[^\]]+\]=.* + +# IServiceProvider +\bI(?=(?:[A-Z][a-z]{2,})+\b) + +# crypt +"\$2[ayb]\$.{56}" + +# scrypt / argon +\$(?:scrypt|argon\d+[di]*)\$\S+ + +# Input to GitHub JSON +content: "[-a-zA-Z=;:/0-9+]*=" + +# Python stringprefix / binaryprefix +# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings +(?v# +(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_])) +# Compiler flags (Scala) +(?:^|[\t ,>"'`=(])-J-[DPWXY](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) +# Compiler flags +#(?:^|[\t ,"'`=(])-[DPWXYLlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) + +# Compiler flags (linker) +,-B +# curl arguments +\b(?:\\n|)curl(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)* +# set arguments +\bset(?:\s+-[abefimouxE]{1,2})*\s+-[abefimouxE]{3,}(?:\s+-[abefimouxE]+)* +# tar arguments +\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+ +# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long... +\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b +# macOS temp folders +/var/folders/\w\w/[+\w]+/(?:T|-Caches-)/ diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index 81bfde27718..bc509a5669e 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -1,28 +1,39 @@ +# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-excludes (?:(?i)\.png$) +(?:^|/)(?i)COPYRIGHT +(?:^|/)(?i)LICEN[CS]E +(?:^|/)3rdparty/ (?:^|/)dirs$ (?:^|/)go\.mod$ (?:^|/)go\.sum$ -(?:^|/)package-lock\.json$ +(?:^|/)package(?:-lock|)\.json$ (?:^|/)sources(?:|\.dep)$ -SUMS$ +(?:^|/)vendor/ +\.a$ \.ai$ +\.avi$ \.bmp$ +\.bz2$ \.cer$ \.class$ \.crl$ \.crt$ \.csr$ \.dll$ +\.docx?$ +\.drawio$ \.DS_Store$ \.eot$ \.eps$ \.exe$ \.gif$ +\.gitattributes$ \.graffle$ \.gz$ \.icns$ \.ico$ \.jar$ +\.jks$ \.jpeg$ \.jpg$ \.key$ @@ -30,28 +41,53 @@ SUMS$ \.lock$ \.map$ \.min\.. +\.mod$ \.mp3$ \.mp4$ +\.o$ +\.ocf$ \.otf$ \.pbxproj$ \.pdf$ \.pem$ +\.png$ \.psd$ +\.pyc$ \.runsettings$ +\.s$ \.sig$ \.so$ \.svg$ \.svgz$ +\.svgz?$ \.tar$ \.tgz$ +\.tiff?$ \.ttf$ \.vsdx$ +\.wav$ +\.webm$ +\.webp$ \.woff +\.woff2?$ \.xcf$ \.xls +\.xlsx?$ \.xpm$ \.yml$ \.zip$ +^\.github/actions/spelling/ +^\.github/fabricbot.json$ +^\.gitignore$ +^\Q.git-blame-ignore-revs\E$ +^\Q.github/workflows/spelling.yml\E$ +^\Qdoc/reference/windows-terminal-logo.ans\E$ +^\Qsamples/ConPTY/EchoCon/EchoCon/EchoCon.vcxproj.filters\E$ +^\Qsrc/host/exe/Host.EXE.vcxproj.filters\E$ +^\Qsrc/host/ft_host/chafa.txt\E$ +^\Qsrc/tools/closetest/CloseTest.vcxproj.filters\E$ +^\XamlStyler.json$ +^build/config/ ^consolegit2gitfilters\.json$ ^dep/ ^doc/reference/master-sequence-list.csv$ @@ -61,12 +97,14 @@ SUMS$ ^src/host/runft\.bat$ ^src/host/runut\.bat$ ^src/interactivity/onecore/BgfxEngine\. +^src/renderer/atlas/ ^src/renderer/wddmcon/WddmConRenderer\. ^src/terminal/adapter/ut_adapter/run\.bat$ ^src/terminal/parser/delfuzzpayload\.bat$ ^src/terminal/parser/ft_fuzzer/run\.bat$ ^src/terminal/parser/ft_fuzzer/VTCommandFuzzer\.cpp$ ^src/terminal/parser/ft_fuzzwrapper/run\.bat$ +^src/terminal/parser/ut_parser/Base64Test.cpp$ ^src/terminal/parser/ut_parser/run\.bat$ ^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$ ^src/tools/lnkd/lnkd\.bat$ @@ -74,6 +112,6 @@ SUMS$ ^src/tools/texttests/fira\.txt$ ^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$ ^src/types/ut_types/UtilsTests.cpp$ -^\.github/actions/spelling/ -^\.gitignore$ -^\XamlStyler.json$ +^tools/ReleaseEngineering/ServicingPipeline.ps1$ +ignore$ +SUMS$ diff --git a/.github/actions/spelling/expect/alphabet.txt b/.github/actions/spelling/expect/alphabet.txt index 47663b0d075..23933713a40 100644 --- a/.github/actions/spelling/expect/alphabet.txt +++ b/.github/actions/spelling/expect/alphabet.txt @@ -5,26 +5,19 @@ AAAAAABBBBBBCCC AAAAABBBBBBCCC abcd abcd -abcde -abcdef -ABCDEFG -ABCDEFGH ABCDEFGHIJ abcdefghijk ABCDEFGHIJKLMNO abcdefghijklmnop ABCDEFGHIJKLMNOPQRST -abcdefghijklmnopqrstuvwxyz ABCG ABE abf BBBBB BBBBBBBB -BBBBBBBBBBBBBBDDDD BBBBBCCC BBBBCCCCC BBGGRR -CCE EFG EFGh QQQQQQQQQQABCDEFGHIJ @@ -33,7 +26,6 @@ QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ qrstuvwxyz qwerty -QWERTYUIOP qwertyuiopasdfg YYYYYYYDDDDDDDDDDD ZAAZZ diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 722631b26ea..8f8f4828d0a 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1,22 +1,19 @@ +aabbcc ABANDONFONT +abbcc +ABCDEFGHIJKLMNOPQRSTUVWXY abgr abi +ABORTIFHUNG ACCESSTOKEN -acec -acf acidev ACIOSS ACover actctx ACTCTXW activatable -ACTIVEBORDER -ACTIVECAPTION -adaa ADDALIAS -ADDB ADDREF -addressof ADDSTRING ADDTOOL AEnd @@ -28,49 +25,39 @@ ahz AImpl AInplace ALIGNRIGHT -alloc allocing +allocs alpc ALTERNATENAME ALTF ALTNUMPAD ALWAYSTIP amd -ansicode ansicpg ANSISYS ANSISYSRC ANSISYSSC -antialias antialiasing ANull anycpu -AOn APARTMENTTHREADED APCs -api APIENTRY apiset APPBARDATA -appconsult -APPICON +appcontainer appium -applet appletname +applets applicationmodel APPLMODAL appmodel -apps APPWINDOW APrep apsect APSTUDIO archeologists -architected argb -argc -args -argv ARRAYSIZE ARROWKEYS asan @@ -80,22 +67,15 @@ ASDF asdfghjkl ASetting ASingle -asm -asmv asmx -aspx -astextplain -AStomps ASYNCWINDOWPOS atch ATest -attr ATTRCOLOR aumid Authenticode AUTOBUDDY AUTOCHECKBOX -autogenerated autohide AUTOHSCROLL automagically @@ -104,35 +84,30 @@ AUTORADIOBUTTON autoscrolling Autowrap AVerify -AVI +AVX awch -azuredevopspodcast azzle -backend backgrounded Backgrounder backgrounding -backport +backported backstory +barbaz Batang -baz Bazz BBDM bbwe bcount -bcrypt bcx bcz BEFOREPARENT beginthread -bgcolor bgfx bgidx Bgk BGR -BGRA +bgra BHID -biblioscape bigobj binplace binplaced @@ -141,15 +116,12 @@ bitcrazed bitflag bitmask BITOPERATION -bitsavers -bitset +bitsets BKCOLOR BKGND Bksp -blog Blt BLUESCROLL -bmp BODGY BOLDFONT BOOLIFY @@ -159,46 +131,40 @@ Borland BOTTOMLEFT BOTTOMRIGHT boutput +boxheader BPBF bpp BPPF branchconfig -BRK +brandings Browsable -bsearch +Bspace bstr BTNFACE -buf bufferout buffersize buflen -bugfix buildtransitive BUILDURI burriter BValue -byref -bytearray bytebuffer cac cacafire -callee capslock CARETBLINKINGENABLED CARRIAGERETURN cascadia -cassert +castsi catid cazamor CBash -cbegin cbiex CBN CBoolean cbt cbuffer CCCBB -ccf cch CCHAR cci @@ -209,17 +175,11 @@ CComp CConsole CConversion CCRT -cctype -CDATA cdd -cdecl CDeclaration CEdit CELLSIZE -cend -cerr cfae -Cfg cfie cfiex cfte @@ -227,100 +187,79 @@ CFuzz cgscrn chafa changelist +chaof charinfo -charlespetzold -charset CHARSETINFO -chcp -checkbox -checkboxes chh -Childitem chk -chrono CHT Cic -cjk -ckuehl -cla +CLA Clcompile CLE cleartype CLICKACTIVE clickdown -climits clipbrd CLIPCHILDREN CLIPSIBLINGS -cliutils -clocale closetest cloudconsole cls CLSCTX -clsid +clsids CLUSTERMAP -cmath cmatrix cmder CMDEXT -Cmdlet -cmdline +cmh CMOUSEBUTTONS -cmp +cmpeq cmt +cmw cmyk CNL cnt CNTRL -codebase Codeflow codepage codepath -codepoint -codeproject -coffgroup -coffgrp +codepoints coinit COLLECTIONURI colorizing -colororacle -colorref -colorscheme +COLORMATRIX +COLORREFs +colorschemes colorspaces colorspec colortable colortbl +colortest colortool COLR combaseapi -combobox comctl COMDAT commandline commctrl commdlg COMMITID -compat componentization conapi conareainfo conattrs conbufferout -concat concfg conclnt conddkrefs condrv conechokey conemu -config configurability conhost -conhostv conime conimeinfo -conint conintegrity conintegrityuwp coninteractivitybase @@ -346,36 +285,30 @@ consolehost CONSOLEIME consoleinternal Consoleroot -Consolescreen CONSOLESETFOREGROUND consoletaeftemplates -CONSOLEV +consoleuwp Consolewait CONSOLEWINDOWOWNER consrv -constexpr constexprable constness contentfiles conterm -CONTEXTMENU contsf contypes convarea conwinuserrefs -coord coordnew COPYCOLOR CORESYSTEM cotaskmem countof -cout CPG cpinfo CPINFOEX CPLINFO cplusplus -cpp CPPCORECHECK cppcorecheckrules cpprest @@ -383,65 +316,45 @@ cpprestsdk cppwinrt CProc cpx -crbegin CREATESCREENBUFFER CREATESTRUCT CREATESTRUCTW -creativecommons cred -cref -crend +crisman CRLFs crloew Crt CRTLIBS csbi csbiex -csharp CSHORT -CSIDL -csproj -Csr +Cspace csrmsg CSRSS csrutil -cstdarg -cstddef -cstdio -cstdlib -cstr -cstring cstyle -csv CSwitch CTerminal CText -ctime ctl ctlseqs -Ctlv -ctor CTRLEVENT +CTRLFREQUENCY CTRLKEYSHORTCUTS -Ctx +CTRLVOLUME Ctxt -ctype CUF cupxy -curated CURRENTFONT currentmode CURRENTPAGE CURSORCOLOR CURSORSIZE CURSORTYPE +CUsers CUU Cwa cwch -cwchar -cwctype -cwd -cxcy CXFRAME CXFULLSCREEN CXHSCROLL @@ -451,10 +364,8 @@ CXSIZE CXSMICON CXVIRTUALSCREEN CXVSCROLL -cxx CYFRAME CYFULLSCREEN -cygwin CYHSCROLL CYMIN CYPADDEDBORDER @@ -465,9 +376,6 @@ CYVIRTUALSCREEN CYVSCROLL dai DATABLOCK -DATAVIEW -DATAW -datetime DBatch dbcs DBCSCHAR @@ -480,12 +388,10 @@ DBGOUTPUT dbh dblclk DBlob -dbproj -DBUILD DColor DCOLORVALUE dcommon -DCompile +dcompile dcompiler DComposition dde @@ -494,37 +400,44 @@ DDevice DEADCHAR dealloc Debian -debolden -debounce debugtype +DECAC DECALN DECANM +DECARM DECAUPSS DECAWM +DECBKM +DECCARA DECCKM DECCOLM +DECCRA +DECCTR DECDHL +decdld DECDWL DECEKBD +DECERA +DECFRA DECID DECKPAM DECKPM DECKPNM DECLRMM -decls -declspec -decltype -declval DECNKM DECNRCM DECOM -deconstructed DECPCTERM +DECPS +DECRARA DECRC DECREQTPARM DECRLM DECRQM -DECRST +DECRQSS +DECRQTSR +decrst +DECSACE DECSASD DECSC DECSCA @@ -533,18 +446,17 @@ DECSCPP DECSCUSR DECSED DECSEL +DECSERA DECSET DECSLPP DECSLRM DECSMKR DECSR -decstandar DECSTBM DECSTR DECSWL DECTCEM -Dedupe -deduplicated +DECXCPR DEFAPP DEFAULTBACKGROUND DEFAULTFOREGROUND @@ -560,39 +472,23 @@ defing DEFPUSHBUTTON defterm DELAYLOAD -deletable DELETEONRELEASE -delims Delt demoable depersist deprioritized -deps -deque -deref -deserialization -deserialize -deserialized -deserializer -deserializing +deserializers desktopwindowxamlsource -dest DESTINATIONNAME -devblogs devicecode -devicefamily -devops Dext DFactory DFF -DFMT dhandler dialogbox -diffing -DINLINE directio DIRECTX -Dirs +DISABLEDELAYEDEXPANSION DISABLENOSCROLL DISPLAYATTRIBUTE DISPLAYATTRIBUTEPROPERTY @@ -600,178 +496,141 @@ DISPLAYCHANGE distro dlg DLGC -dll -dllexport DLLGETVERSIONPROC -dllimport dllinit dllmain DLLVERSIONINFO DLOAD DLOOK dmp -DOCTYPE -docx DONTCARE doskey dotnet -doubleclick -downlevel -dpg -dpi +DPG DPIAPI DPICHANGE DPICHANGED +DPIs dpix dpiy +dpnx DRAWFRAME DRAWITEM DRAWITEMSTRUCT drcs -dropdown -DROPDOWNLIST DROPFILES drv +DSBCAPS +DSBLOCK +DSBPLAY +DSBUFFERDESC +DSBVOLUME dsm -dst +dsound +DSSCL DSwap DTest -dtor DTTERM DUMMYUNIONNAME -DUNICODE -DUNIT dup'ed dvi dwl DWLP dwm dwmapi -dword +DWORDs dwrite -dwriteglyphrundescriptionclustermap dxgi dxgidwm +dxguid dxinterop +dxsm dxttbmp -eachother -eae -eaf +Dyreen EASTEUROPE ECH echokey ecount ECpp +ect Edgium EDITKEYS EDITTEXT EDITUPDATE edputil -edu Efast EHsc +EINS EJO ELEMENTNOTAVAILABLE elems -elif -elseif emacs -emplate EMPTYBOX enabledelayedexpansion -endian -endif -endl -endlocal endptr endregion -ENQ -enqueuing -entrypoint +ENTIREBUFFER +entrypoints ENU -enum ENUMLOGFONT ENUMLOGFONTEX enumranges -envvar -eol +eplace EPres +EQU ERASEBKGND -errno -errorlevel -ETB etcoreapp ETW -ETX EUDC EVENTID eventing everytime evflags evt -ewdelete -exe execd -executables executionengine exemain EXETYPE +exeuwp exewin exitwin expectedinput -expr EXPUNGECOMMANDHISTORY EXSTYLE EXTENDEDEDITKEY EXTKEY EXTTEXTOUT -fabricbot facename FACENODE FACESIZE -failfast FAILIFTHERE -fallthrough -FARPROC fastlink -fcb fcharset -fclose -fcntl -fdc -FDD -fdopen fdw fesb FFDE FFrom +fgbg FGCOLOR -fgetc -fgetwc FGHIJ fgidx +FGs FILEDESCRIPTION -fileno -filepath FILESUBTYPE FILESYSPATH -filesystem -FILETIME -FILETYPE fileurl FILEW FILLATTR FILLCONSOLEOUTPUT FILTERONPASTE -finalizer FINDCASE FINDDLG FINDDOWN -FINDSTR FINDSTRINGEXACT FINDUP FIter FIXEDCONVERTED +FIXEDFILEINFO Flg flyout fmodern @@ -785,7 +644,7 @@ FONTENUMPROC FONTFACE FONTFAMILY FONTHEIGHT -fontlist +fontinfo FONTOK FONTSIZE FONTSTRING @@ -794,28 +653,21 @@ FONTTYPE FONTWEIGHT FONTWIDTH FONTWINDOW -forceinline +fooo FORCEOFFFEEDBACK FORCEONFEEDBACK -FORCEV -foreach -fprintf framebuffer FRAMECHANGED fre -freopen -frontend +frontends fsanitize Fscreen FSCTL FSINFOCLASS -fsproj -fstream fte Ftm -fullscreen +Fullscreens fullwidth -func FUNCTIONCALL fuzzer fuzzmain @@ -827,11 +679,10 @@ fwlink GAUSSIAN gci gcx -gcy gdi gdip gdirenderer -GENPROFILE +Geddy geopol GETALIAS GETALIASES @@ -841,7 +692,6 @@ GETALIASEXESLENGTH GETAUTOHIDEBAREX GETCARETWIDTH getch -getchar GETCLIENTAREAANIMATION GETCOMMANDHISTORY GETCOMMANDHISTORYLENGTH @@ -866,7 +716,6 @@ GETKEYBOARDLAYOUTNAME GETKEYSTATE GETLARGESTWINDOWSIZE GETLBTEXT -getline GETMINMAXINFO GETMOUSEINFO GETMOUSEVANISH @@ -876,8 +725,6 @@ GETOBJECT GETPOS GETSELECTIONINFO getset -GETSTATE -GETTEXT GETTEXTLEN GETTITLE GETWAITTOKILLSERVICETIMEOUT @@ -885,38 +732,33 @@ GETWAITTOKILLTIMEOUT GETWHEELSCROLLCHARACTERS GETWHEELSCROLLCHARS GETWHEELSCROLLLINES -getwriter GFEh Gfun gfx +GGI GHIJK GHIJKL GHIJKLM gitfilters -github -gitlab +gitmodules gle -globals -gmail +GLOBALFOCUS +GLYPHENTRY GMEM GNUC Goldmine gonce -Google goutput -GPUs -grayscale GREENSCROLL Grehan -grep Greyscale gridline groupbox gset gsl GTP +GTR guc -gui guidatom GValue GWL @@ -924,11 +766,8 @@ GWLP gwsz HABCDEF Hackathon -halfwidth HALTCOND HANGEUL -hardcoded -hardcodes hashalg HASSTRINGS hbitmap @@ -943,19 +782,16 @@ hdr HDROP hdrstop HEIGHTSCROLL -hfile hfont +hfontresource hglobal hhh -HHmm hhook hhx HIBYTE -HICON +hicon HIDEWINDOW -HIGHLIGHTTEXT hinst -HINSTANCE Hirots HISTORYBUFS HISTORYNODUP @@ -968,41 +804,31 @@ hkl HKLM hlocal hlsl -HMENU hmod hmodule hmon -HMONITOR -horiz HORZ hostable hostlib -Hostx HPA -HPAINTBUFFER -hpcon +HPCON hpj -hpp HPR -HPROPSHEETPAGE HProvider HREDRAW hresult -HRSRC +hrottled hscroll hsl hstr hstring -hsv HTBOTTOMLEFT HTBOTTOMRIGHT HTCAPTION HTCLIENT HTLEFT -htm HTMAXBUTTON HTMINBUTTON -html HTMLTo HTRIGHT HTTOP @@ -1013,35 +839,17 @@ HVP hwheel hwnd HWNDPARENT -hxx -IAccessibility -IAction -IApi -IApplication -IBase -icacls iccex -icch -IChar -ico -IComponent +icket ICONERROR Iconified ICONINFORMATION IConsole ICONSTOP -IControl ICONWARNING -ICore -IData IDCANCEL IDD -IDesktop -IDevice -IDictionary IDISHWND -IDispatch -IDisposable idl idllib IDOK @@ -1049,35 +857,18 @@ IDR idth idx IDXGI -IDynamic IEnd IEnum -IEnumerable -ies -ietf IFACEMETHODIMP -ifdef ification -ifndef -IFont -ifstream IGNOREEND -IHigh +IGNORELANGUAGE IHosted iid -IInitialize -IInput -IInspectable -IInteract -IInteractivity IIo -IList -imagemagick -Imatch ime Imm -IMouse -impl +IMPEXP inbox inclusivity INCONTEXT @@ -1085,103 +876,59 @@ INFOEX inheritcursor inheritdoc inheritfrom -ini INITCOMMONCONTROLSEX INITDIALOG initguid INITMENU inkscape -inl INLINEPREFIX inlines -INotify -inout -inplace inproc Inputkeyinfo INPUTPROCESSORPROFILE inputrc Inputreadhandledata INSERTMODE -installationpath -intellisense INTERACTIVITYBASE INTERCEPTCOPYPASTE INTERNALNAME -interop -interoperability inthread -intptr intsafe INVALIDARG INVALIDATERECT -inwap -IObservable ioctl -iomanip -iostream -iot ipch -ipconfig -IPersist ipp IProperty IPSINK ipsp -IRaw -IRead -IReference -IRender -IRenderer -IScheme -ISelection IShell -issuecomment -IState -IStoryboard -isupper ISwap -iswdigit -iswspace -ISystem iterm itermcolors ITerminal -IText itf Ith itoa IUI -IUia IUnknown ivalid -IValue -IVector -IWait -iwch -IWeb -IWin -IWindow -IXaml +IWIC IXMP +IXP jconcpp JOBOBJECT JOBOBJECTINFOCLASS jpe -jpeg -jpg JPN -json -jsonc jsoncpp +Jsons jsprovider jumplist KAttrs kawa -kayla Kazu kazum -kbd kcub kcud kcuf @@ -1189,13 +936,11 @@ kcuu kernelbase kernelbasestaging KEYBDINPUT -keybinding keychord keydown keyevent KEYFIRST KEYLAST -keymap Keymapping keyscan keystate @@ -1208,26 +953,26 @@ KLF KLMNO KLMNOPQRST KLMNOPQRSTQQQQQ +KOK +KPRIORITY KVM langid LANGUAGELIST lasterror lastexitcode LAYOUTRTL +lbl LBN -LBound LBUTTON LBUTTONDBLCLK LBUTTONDOWN LBUTTONUP lcb +lci LCONTROL LCTRL lcx LEFTALIGN -LEFTSHIFT -len -lhs libpopcnt libsancov libtickit @@ -1237,17 +982,11 @@ LINESELECTION LINEWRAP LINKERRCAP LINKERROR -linkid -linkpath linputfile -Linq -linux -listbox listproperties listptr listptrsize lld -LLVM llx LMENU LMNOP @@ -1256,40 +995,31 @@ lnkd lnkfile LNM LOADONCALL +loadu LOBYTE localappdata -localhost locsrc -locstudio Loewen LOGFONT +LOGFONTA LOGFONTW logissue -lowercased loword lparam -lparen -LPBYTE LPCCH lpch -LPCHARSETINFO -LPCOLORREF LPCPLINFO LPCREATESTRUCT lpcs -LPCSTR LPCTSTR -LPCWSTR lpdata LPDBLIST lpdis LPDRAWITEMSTRUCT lpdw -LPDWORD lpelfe lpfn LPFNADDPROPSHEETPAGE -LPINT lpl LPMEASUREITEMSTRUCT LPMINMAXINFO @@ -1299,43 +1029,38 @@ LPNEWCPLINFOA LPNEWCPLINFOW LPNMHDR lpntme -LPPOINT LPPROC LPPROPSHEETPAGE LPPSHNOTIFY lprc -LPRECT lpstr lpsz LPTSTR LPTTFONTLIST lpv -LPVOID LPW LPWCH +lpwfx LPWINDOWPOS lpwpos lpwstr LRESULT -lru lsb lsconfig -lsproj lss lstatus lstrcmp lstrcmpi LTEXT LTLTLTLTL -ltype LUID +luma lval LVB LVERTICAL LWA LWIN lwkmvj -mailto majorly makeappx MAKEINTRESOURCE @@ -1344,15 +1069,13 @@ MAKELANGID MAKELONG MAKELPARAM MAKELRESULT -malloc -manpage MAPBITMAP MAPVIRTUALKEY MAPVK MAXDIMENSTRING maxing -MAXLENGTH MAXSHORT +maxval maxversiontested MAXWORD maybenull @@ -1366,104 +1089,75 @@ MDs MEASUREITEM megamix memallocator -memcmp -memcpy -memmove -memset MENUCHAR MENUCONTROL MENUDROPALIGNMENT -MENUITEM MENUITEMINFO MENUSELECT -Mersenne messageext -metadata metaproj midl mii MIIM milli -mimetype mincore mindbogglingly -mingw minimizeall minkernel MINMAXINFO minwin minwindef Mip -mkdir MMBB mmcc MMCPL -MMdd mmsystem MNC MNOPQ MNOPQR MODALFRAME -modelproj MODERNCORE MONITORINFO MONITORINFOEXW MONITORINFOF -monospaced -monostate MOUSEACTIVATE MOUSEFIRST MOUSEHWHEEL MOUSEMOVE -mousewheel +movemask MOVESTART msb -msbuild -mscorlib msctf msctls msdata -msdn msft MSGCMDLINEF MSGF MSGFILTER MSGFLG MSGMARKMODE -MSGS MSGSCROLLMODE MSGSELECTMODE msiexec MSIL msix msrc -msvcrt MSVCRTD -MSVS msys -msysgit -mui -Mul -multiline +MTSM munged munges -mutex -mutexes +murmurhash muxes myapplet mydir -myignite MYMAX Mypair Myval NAMELENGTH nameof -namespace -namespaced namestream -nano natvis -nbsp NCCALCSIZE NCCREATE NCLBUTTONDOWN @@ -1475,16 +1169,12 @@ NCRBUTTONDOWN NCRBUTTONUP NCXBUTTONDOWN NCXBUTTONUP -NDEBUG -ned NEL -NEQ netcoreapp netstandard NEWCPLINFO NEWCPLINFOA NEWCPLINFOW -newcursor Newdelete NEWINQUIRE NEWINQURE @@ -1494,21 +1184,16 @@ NEWTEXTMETRICEX Newtonsoft NEXTLINE nfe -nlength -Nls NLSMODE +nnn NOACTIVATE NOAPPLYNOW NOCLIP -NOCOLOR NOCOMM NOCONTEXTHELP NOCOPYBITS -nodiscard NODUP -noexcept -NOHELP -noinline +noexcepts NOINTEGRALHEIGHT NOINTERFACE NOLINKINFO @@ -1524,13 +1209,13 @@ NONINFRINGEMENT NONPREROTATED nonspace NOOWNERZORDER +Nop NOPAINT NOPQRST noprofile NOREDRAW NOREMOVE NOREPOSITION -noreturn NORMALDISPLAY NOSCRATCH NOSEARCH @@ -1539,22 +1224,17 @@ NOSENDCHANGING NOSIZE NOSNAPSHOT NOTHOUSANDS -nothrow NOTICKS +NOTIMEOUTIFNOTHUNG NOTIMPL -notin -NOTNULL NOTOPMOST NOTRACK NOTSUPPORTED nouicompat nounihan NOUPDATE -NOWAIT NOYIELD NOZORDER -NPM -npos nrcs NSTATUS ntapi @@ -1566,6 +1246,7 @@ ntdll ntifs ntlpcapi ntm +nto ntrtl ntstatus ntsubauth @@ -1576,29 +1257,25 @@ ntuser NTVDM ntverp NTWIN -nuget +nugetversions +nullability nullness nullonfailure -nullopt -nullptr +nullopts NULs numlock numpad NUMSCROLL nupkg -nuspec NVIDIA -NVR OACR -oauth objbase -ocf ocolor odl -oem oemcp OEMFONT OEMFORMAT +OEMs offboarded OLEAUT OLECHAR @@ -1611,6 +1288,7 @@ onecoreuapuuid onecoreuuid ONECOREWINDOWS onehalf +oneseq ONLCR openbash opencode @@ -1620,9 +1298,7 @@ openconsoleproxy OPENIF OPENLINK openps -opensource openvt -openxmlformats ORIGINALFILENAME osc OSCBG @@ -1633,24 +1309,21 @@ OSCSCB OSCSCC OSCWT OSDEPENDSROOT -osfhandle OSG OSGENG osign oss -ostream -ostringstream +otepad ouicompat +OUnter outdir -outfile -Outof OUTOFCONTEXT -OUTOFMEMORY -outout Outptr +outstr OVERLAPPEDWINDOW OWNDC OWNERDRAWFIXED +packagename packageuwp PACKCOORD PACKVERSION @@ -1660,23 +1333,20 @@ PAINTPARAMS PAINTSTRUCT PALPC pankaj -params parentable parms passthrough PATCOPY pathcch PATTERNID -PBOOL -PBYTE pcat pcb pcch PCCHAR PCCONSOLE PCD +pcg pch -PCHAR PCIDLIST PCIS PCLIENT @@ -1698,18 +1368,13 @@ PCWCH PCWCHAR PCWSTR pda -pdb -pdbonly +Pdbs pdbstr -pdf -pdp pdtobj pdw -PDWORD pdx peb PEMAGIC -PENDTASKMSG pfa PFACENODE pfed @@ -1722,17 +1387,14 @@ PFONTENUMDATA PFS pgd pgdn -pgorepro -pgort -PGU +PGONu pguid pgup -PHANDLE phhook phwnd -pid pidl PIDLIST +pids pii pinvoke pipename @@ -1741,14 +1403,12 @@ pixelheight PIXELSLIST PJOBOBJECT pkey -placeholders platforming playsound -plist +ploc +ploca +plocm PLOGICAL -plugin -PMv -png pnm PNMLINK pntm @@ -1757,24 +1417,20 @@ POBJECT Podcast POINTSLIST POLYTEXTW -popd -POPF poppack -popup POPUPATTR +popups PORFLG positionals -posix POSTCHARBREAKS POSX POSXSCROLL POSYSCROLL -ppci +PPEB ppf ppguid ppidl pplx -PPORT PPROC PPROCESS ppropvar @@ -1785,19 +1441,12 @@ ppsz ppv ppwch PQRST -pragma prc prealigned -prebuilt -precendence -precomp prect prefast -prefilled prefs preinstalled -PRELOAD -PREMULTIPLIED prepopulated presorted PREVENTPINNING @@ -1806,14 +1455,11 @@ PREVIEWWINDOW PREVLINE prg pri -printf prioritization processenv processhost PROCESSINFOCLASS procs -Progman -proj PROPERTYID PROPERTYKEY PROPERTYVAL @@ -1827,7 +1473,6 @@ propvar propvariant propvarutil psa -psd PSECURITY pseudocode pseudoconsole @@ -1835,13 +1480,10 @@ pseudoterminal psh pshn PSHNOTIFY -PSHORT pshpack PSINGLE psl psldl -psm -PSMALL PSNRET PSobject psp @@ -1850,45 +1492,36 @@ psr PSTR psz ptch -ptr -ptrdiff +ptrs ptsz PTYIn PUCHAR -PULONG PUNICODE -pushd -putchar -putwchar -PVOID pwch -PWCHAR PWDDMCONSOLECONTEXT -PWORD pws -pwsh pwstr pwsz pythonw +Qaabbcc qos QRSTU -qsort -queryable +QUERYOPEN QUESTIONMARK quickedit +QUZ QWER +Qxxxxxxxxxxxxxxx qzmp RAII RALT rasterbar rasterfont rasterization -rawinput RAWPATH raytracers razzlerc rbar -rbegin RBUTTON RBUTTONDBLCLK RBUTTONDOWN @@ -1904,44 +1537,30 @@ RCOCW RCONTROL RCOW rcv -rdbuf -RDONLY -rdpartysource readback READCONSOLE READCONSOLEOUTPUT READCONSOLEOUTPUTSTRING -Readline -readme READMODE -readonly -READWRITE -realloc +reallocs reamapping rects redef +redefinable Redir -redirector redist -redistributable REDSCROLL -refactor -refactoring REFCLSID -refcount -referencesource REFGUID REFIID REFPROPERTYKEY -regex REGISTEROS REGISTERVDM regkey REGSTR reingest -Relayout RELBINPATH -Remoting +remoting renamer renderengine rendersize @@ -1950,22 +1569,20 @@ reparenting replatformed Replymessage repositorypath +Requiresx rescap Resequence RESETCONTENT resheader -resizable resmimetype -restrictedcapabilities resw resx -retval rfa -rfc +rfid rftp -rgb -rgba +RGBCOLOR rgbi +rgbs rgci rgfae rgfte @@ -1978,47 +1595,45 @@ rgs rgui rgw rgwch -rhs RIGHTALIGN RIGHTBUTTON riid +Rike RIPMSG RIS -RMENU roadmap robomac -roundtrip -rparen +rosetta +roundtrips RRF RRRGGGBB rsas rtcore RTEXT -rtf RTFTo -Rtl RTLREADING +Rtn RTTI ruleset runas -runasradio RUNDLL runformat runft RUNFULLSCREEN +runfuzz runsettings -runtests +runtest runtimeclass runuia runut runxamlformat -rvalue RVERTICAL +rvpa RWIN rxvt safearray -SAFECAST safemath +sapi sba SBCS SBCSDBCS @@ -2029,19 +1644,15 @@ scancode scanline schemename SCL -scm -scprintf SCRBUF SCRBUFSIZE screenbuffer SCREENBUFFERINFO screeninfo -screenshot +screenshots scriptload -Scrollable scrollback -scrollbar -Scroller +scrollbars SCROLLFORWARD SCROLLINFO scrolllock @@ -2051,18 +1662,14 @@ SCROLLSCREENBUFFER scursor sddl sdeleted -sdk SDKDDK -searchbox securityappcontainer segfault SELCHANGE SELECTALL -selectany SELECTEDFONT SELECTSTRING Selfhosters -serializers SERVERDLL SETACTIVE SETBUDDYINT @@ -2073,7 +1680,6 @@ SETCURSOR SETCURSORINFO SETCURSORPOSITION SETDISPLAYMODE -setfill SETFOCUS SETFONT SETFOREGROUND @@ -2084,28 +1690,24 @@ setintegritylevel SETITEMDATA SETITEMHEIGHT SETKEYSHORTCUTS -setlocal -setlocale SETMENUCLOSE -setmode SETNUMBEROFCOMMANDS SETOS SETPALETTE -SETPOS SETRANGE SETSCREENBUFFERSIZE SETSEL SETTEXTATTRIBUTE SETTINGCHANGE -SETTITLE -setw Setwindow SETWINDOWINFO +SFGAO +SFGAOF sfi SFINAE +SFolder SFUI sgr -SHANDLE SHCo shcore shellapi @@ -2113,7 +1715,6 @@ shellex shellscalingapi SHFILEINFO SHGFI -SHGFP SHIFTJIS Shl shlguid @@ -2121,21 +1722,21 @@ shlobj shlwapi SHORTPATH SHOWCURSOR +SHOWDEFAULT SHOWMAXIMIZED SHOWMINNOACTIVE +SHOWNA SHOWNOACTIVATE SHOWNORMAL SHOWWINDOW -SHRT -sid sidebyside SIF SIGDN SINGLEFLAG SINGLETHREADED siup +sixel SIZEBOX -sizeof SIZESCROLL SKIPFONT SKIPOWNPROCESS @@ -2156,29 +1757,19 @@ Solutiondir somefile SOURCEBRANCH sourced -SOURCESDIRECTORY -SPACEBAR spammy spand -sprintf -sqlproj -srand -src SRCCODEPAGE SRCCOPY SRCINVERT srcsrv SRCSRVTRG srctool -sre srect srv srvinit srvpipe -ssh -sstream -stackoverflow -standalone +ssa STARTF STARTUPINFO STARTUPINFOEX @@ -2190,55 +1781,37 @@ STARTWPARMSW Statusline stdafx STDAPI -stdcall +stdc stdcpp -stderr -stdexcept -stdin -stdio STDMETHODCALLTYPE STDMETHODIMP -stdout -stgm +STGM stl -stoi -stol -stoul stoutapot -strikethrough -stringstream +Stri +Stringable STRINGTABLE -strlen strrev strsafe -strtok -structs +STUBHEAD STUVWX -STX stylecop SUA subcompartment -subfolder +subfolders subkey SUBLANG -sublicensable -submenu subresource -subspan -substr subsystemconsole subsystemwindows suiteless -svg swapchain swapchainpanel swappable SWMR SWP -swprintf SYMED SYNCPAINT -sys syscalls SYSCHAR SYSCOMMAND @@ -2258,8 +1831,6 @@ TARG targetentrypoint TARGETLIBS TARGETNAME -targetnametoken -targetsize targetver taskbar tbar @@ -2274,23 +1845,20 @@ TCI tcome tcommandline tcommands +Tdd TDelegated TDP TEAMPROJECT tearoff Teb -techcommunity -technet tellp -telnet -telnetd -templated teraflop terminalcore +terminalinput +terminalrenderdata TERMINALSCROLLING terminfo TEs -testapp testbuildplatform testcon testd @@ -2299,11 +1867,9 @@ testenv testlab testlist testmd -testmddefinition testmode testname testnameprefix -testnetv TESTNULL testpass testpasses @@ -2315,7 +1881,6 @@ texel TExpected textattribute TEXTATTRIBUTEID -textbox textboxes textbuffer TEXTINCLUDE @@ -2323,102 +1888,82 @@ textinfo TEXTMETRIC TEXTMETRICW textmode +texttests TFCAT tfoo TFunction tga -threadpool THUMBPOSITION THUMBTRACK TIcon -tif tilunittests -Timeline titlebar TITLEISLINKNAME TJson TLambda +TLDP TLEN -Tlg Tlgdata TMAE TMPF TMult tmultiple -tmux -todo +TODOs tofrom tokenhelpers -tokenized -tokenizing toolbars TOOLINFO -Toolset -tooltip +TOOLWINDOW TOPDOWNDIB TOPLEFT -toplevel TOPRIGHT TOpt tosign touchpad -towlower -towupper Tpp Tpqrst tprivapi tracelog tracelogging traceloggingprovider +traceviewpp trackbar TRACKCOMPOSITION trackpad -transcoder transitioning Trd TREX triaged triaging TRIANGLESTRIP +Tribool TRIMZEROHEADINGS -truetype trx tsattrs tsf +tsgr TStr TSTRFORMAT TSub TTBITMAP -ttf TTFONT TTFONTLIST tthe tthis TTM TTo -TVPP +tvpp Txtev typechecked -typechecking -typedef -typeid -typeinfo typelib -typename -typeof typeparam TYUI UAC uap uapadmin UAX -ubuntu ucd -ucd -ucdxml uch -UCHAR -ucs udk UDM uer @@ -2427,41 +1972,30 @@ uia UIACCESS uiacore uiautomationcore -Uid uielem UIELEMENTENABLEDONLY -uint -uintptr +UINTs ulcch -ulong +umul +umulh Unadvise unattend -uncomment UNCPRIORITY -undef -Unescape unexpand -Unfocus unhighlighting unhosted -unicode -UNICODESTRING UNICODETEXT UNICRT -uninit uninitialize -uninstall +Unintense Uniscribe -unittest unittesting -universaltest +unittests unk unknwn unmark UNORM unparseable -unpause -Unregister unregistering untests untextured @@ -2470,12 +2004,6 @@ UPDATEDISPLAY UPDOWN UPKEY UPSS -upvote -uri -url -urlencoded -Urxvt -USASCII usebackq USECALLBACK USECOLOR @@ -2489,42 +2017,29 @@ USEPOSITION userbase USERDATA userdpiapi -username Userp userprivapi -userprofile USERSRV USESHOWWINDOW USESIZE USESTDHANDLES -ushort +usp USRDLL -utf -utils utr -uuid -uuidof -uuidv UVWX UVWXY -UWA +uwa uwp uxtheme -vals Vanara vararg -vbproj vclib -Vcount vcpkg vcprintf -vcproj -vcvarsall vcxitems -vcxproj vec +vectorized VERCTRL -versioning VERTBAR VFT vga @@ -2533,31 +2048,28 @@ viewkind viewports Virt VIRTTERM -Virtualizing vkey VKKEYSCAN VMs VPA -VPATH VPR -VPrintf VProc VRaw VREDRAW vsc +vsconfig vscprintf VSCROLL +vsdevshell vsinfo -vsnprintf vso vspath -vsprintf VSTAMP vstest VSTS VSTT -vstudio vswhere +vtapi vtapp VTE VTID @@ -2574,59 +2086,43 @@ vttest VWX waaay waitable -waivable WANSUNG WANTARROWS WANTTAB wapproj -wav +WAVEFORMATEX wbuilder wch -wchar +wchars WCIA WCIW -WClass -wcout -wcschr -wcscmp -wcscpy WCSHELPER wcsicmp -wcslen wcsnicmp -wcsrchr wcsrev -wcstod -wcstoul wddm wddmcon -wddmconrenderer WDDMCONSOLECONTEXT wdm webpage -website -websocket +websites +websockets wekyb -WEOF wex wextest wextestclass -wfdopen WFill wfopen -wfstream WHelper -whitelisting +wic WIDTHSCROLL Widthx -wiki -wikia -wikipedia wil WImpl WINAPI winbase winbasep +wincodec wincon winconp winconpty @@ -2636,14 +2132,12 @@ wincontypes WINCORE windbg WINDEF -WINDIR windll WINDOWALPHA Windowbuffer windowdpiapi WINDOWEDGE windowext -WINDOWFRAME windowime WINDOWINFO windowio @@ -2657,12 +2151,12 @@ windowrect windowsapp windowsinternalstring WINDOWSIZE +windowsshell +windowsterminal windowsx -WINDOWTEXT windowtheme WINDOWTITLE winevent -winfx wingdi winget WINIDE @@ -2682,10 +2176,8 @@ winuser winuserp WINVER wistd -wixproj -wline -wlinestream wmain +wmemory WMSZ wnd WNDALLOC @@ -2698,9 +2190,6 @@ WNull wnwb workarea workaround -workflow -workitem -wostream WOutside WOWARM WOWx @@ -2710,29 +2199,24 @@ wpf WPR WPrep WPresent -wprintf wprp wprpi wregex -WResult writeback writechar WRITECONSOLE WRITECONSOLEINPUT WRITECONSOLEOUTPUT WRITECONSOLEOUTPUTSTRING +wrkstr wrl wrp WRunoff WScript wsl WSLENV -wsmatch -WSpace -wss wstr -wstring -wstringstream +wstrings wsz wtd WTest @@ -2741,18 +2225,20 @@ WTo wtof wtoi WTs +WTSOFTFONT wtw wtypes Wubi WUX WVerify -wwaproj WWith wxh +wyhash +wymix +wyr xact -xaml Xamlmeta -xargs +xamls xaz xbf xbutton @@ -2767,52 +2253,44 @@ XCount xdy XEncoding xes -Xes xff XFile XFORM +xin +xinchaof +xinxinchaof XManifest XMath XMFLOAT -xml -xmlns -xor -xorg xorg -Xpath -XPosition XResource -xsd xsi -xsize xstyler XSubstantial xtended -xterm XTest XTPOPSGR XTPUSHSGR xtr +XTWINOPS xunit xutr -xvalue XVIRTUALSCREEN XWalk -Xzn +xwwyzz +xxyyzz yact -YAML YCast YCENTER YCount YDPI -yml YOffset -YPosition -YSize YSubstantial YVIRTUALSCREEN YWalk +Zabcdefghijklmnopqrstuvwxyz ZCmd ZCtrl -zsh zxcvbnm +ZYXWVU +ZYXWVUTd diff --git a/.github/actions/spelling/expect/web.txt b/.github/actions/spelling/expect/web.txt index f50d31a6e0a..52c1cfd1f0f 100644 --- a/.github/actions/spelling/expect/web.txt +++ b/.github/actions/spelling/expect/web.txt @@ -1,16 +1,6 @@ -http -www -ecma -rapidtables WCAG -freedesktop -ycombinator -robertelder -kovidgoyal -leonerd -fixterms winui appshellintegration -cppreference +mdtauk gfycat Guake diff --git a/.github/actions/spelling/line_forbidden.patterns b/.github/actions/spelling/line_forbidden.patterns new file mode 100644 index 00000000000..31ad2ddcd26 --- /dev/null +++ b/.github/actions/spelling/line_forbidden.patterns @@ -0,0 +1,62 @@ +# reject `m_data` as there's a certain OS which has evil defines that break things if it's used elsewhere +# \bm_data\b + +# If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test, +# you might not want to check in code where you were debugging w/ `fit()`, in which case, you might want +# to use this: +#\bfit\( + +# s.b. GitHub +\bGithub\b + +# s.b. GitLab +\bGitlab\b + +# s.b. JavaScript +\bJavascript\b + +# s.b. Microsoft +\bMicroSoft\b + +# s.b. another +\ban[- ]other\b + +# s.b. greater than +\bgreater then\b + +# s.b. into +#\sin to\s + +# s.b. opt-in +\sopt in\s + +# s.b. less than +\bless then\b + +# s.b. otherwise +\bother[- ]wise\b + +# s.b. nonexistent +\bnon existing\b +\b[Nn]o[nt][- ]existent\b + +# s.b. preexisting +[Pp]re[- ]existing + +# s.b. preempt +[Pp]re[- ]empt\b + +# s.b. preemptively +[Pp]re[- ]emptively + +# s.b. reentrancy +[Rr]e[- ]entrancy + +# s.b. reentrant +[Rr]e[- ]entrant + +# s.b. workaround(s) +#\bwork[- ]arounds?\b + +# Reject duplicate words +\s([A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})\s\g{-1}\s diff --git a/.github/actions/spelling/patterns/patterns.txt b/.github/actions/spelling/patterns/patterns.txt index bee4446d46f..a0e1931f36f 100644 --- a/.github/actions/spelling/patterns/patterns.txt +++ b/.github/actions/spelling/patterns/patterns.txt @@ -1,11 +1,6 @@ -https://(?:(?:[-a-zA-Z0-9?&=]*\.|)microsoft\.com)/[-a-zA-Z0-9?&=_#\/.]* -https://aka\.ms/[-a-zA-Z0-9?&=\/_]* -https://www\.itscj\.ipsj\.or\.jp/iso-ir/[-0-9]+\.pdf -https://www\.vt100\.net/docs/[-a-zA-Z0-9#_\/.]* -https://www.w3.org/[-a-zA-Z0-9?&=\/_#]* -https://(?:(?:www\.|)youtube\.com|youtu.be)/[-a-zA-Z0-9?&=]* -https://(?:[a-z-]+\.|)github(?:usercontent|)\.com/[-a-zA-Z0-9?%&=_\/.]* -https://www.xfree86.org/[-a-zA-Z0-9?&=\/_#]* +# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns + +https?://\S+ [Pp]ublicKeyToken="?[0-9a-fA-F]{16}"? (?:[{"]|UniqueIdentifier>)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}(?:[}"]|v# +(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_])) + +# hit-count: 20 file-count: 9 +# hex runs +\b[0-9a-fA-F]{16,}\b + +# hit-count: 10 file-count: 7 +# uuid: +\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b + +# hit-count: 4 file-count: 4 +# mailto urls +mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} + +# hit-count: 4 file-count: 1 +# ANSI color codes +(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m + +# hit-count: 2 file-count: 1 +# latex +\\(?:n(?:ew|ormal|osub)|r(?:enew)|t(?:able(?:of|)|he|itle))(?=[a-z]+) + +# hit-count: 1 file-count: 1 +# hex digits including css/html color classes: +(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b + +# hit-count: 1 file-count: 1 +# Non-English +[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]* + +# hit-count: 1 file-count: 1 +# French +# This corpus only had capital letters, but you probably want lowercase ones as well. +\b[LN]'+[a-z]{2,}\b + +# acceptable duplicates +# ls directory listings +[-bcdlpsw](?:[-r][-w][-sx]){3}\s+\d+\s+(\S+)\s+\g{-1}\s+\d+\s+ +# C/idl types + English ... +\s(Guid|long|LONG|that) \g{-1}\s + +# javadoc / .net +(?:[\\@](?:groupname|param)|(?:public|private)(?:\s+static|\s+readonly)*)\s+(\w+)\s+\g{-1}\s + +# Commit message -- Signed-off-by and friends +^\s*(?:(?:Based-on-patch|Co-authored|Helped|Mentored|Reported|Reviewed|Signed-off)-by|Thanks-to): (?:[^<]*<[^>]*>|[^<]*)\s*$ + +# Autogenerated revert commit message +^This reverts commit [0-9a-f]{40}\.$ + +# vtmode +--vtmode\s+(\w+)\s+\g{-1}\s + +# ignore long runs of a single character: +\b([A-Za-z])\g{-1}{3,}\b diff --git a/.github/actions/spelling/reject.txt b/.github/actions/spelling/reject.txt index e2763f35a82..301719de47e 100644 --- a/.github/actions/spelling/reject.txt +++ b/.github/actions/spelling/reject.txt @@ -1,22 +1,12 @@ ^attache$ ^attacher$ ^attachers$ -^spae$ -^spaebook$ -^spaecraft$ -^spaed$ -^spaedom$ -^spaeing$ -^spaeings$ -^spae-man$ -^spaeman$ -^spaer$ -^Spaerobee$ -^spaes$ -^spaewife$ -^spaewoman$ -^spaework$ -^spaewright$ -^wether$ -^wethers$ -^wetherteg$ +benefitting +occurences? +^dependan.* +^oer$ +Sorce +^[Ss]pae.* +^untill$ +^untilling$ +^wether.* diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml deleted file mode 100644 index cbc72ae28ea..00000000000 --- a/.github/workflows/spelling.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Spell checking -on: - pull_request_target: - push: - -jobs: - build: - name: Spell checking - runs-on: ubuntu-latest - steps: - - name: checkout-merge - if: "contains(github.event_name, 'pull_request')" - uses: actions/checkout@v2.0.0 - with: - ref: refs/pull/${{github.event.pull_request.number}}/merge - - name: checkout - if: "!contains(github.event_name, 'pull_request')" - uses: actions/checkout@v2.0.0 - - uses: check-spelling/check-spelling@v0.0.18 diff --git a/.github/workflows/spelling2.yml b/.github/workflows/spelling2.yml new file mode 100644 index 00000000000..446b24343ed --- /dev/null +++ b/.github/workflows/spelling2.yml @@ -0,0 +1,134 @@ +# spelling.yml is blocked per https://github.com/check-spelling/check-spelling/security/advisories/GHSA-g86g-chm8-7r2p +name: Spell checking + +# Comment management is handled through a secondary job, for details see: +# https://github.com/check-spelling/check-spelling/wiki/Feature%3A-Restricted-Permissions +# +# `jobs.comment-push` runs when a push is made to a repository and the `jobs.spelling` job needs to make a comment +# (in odd cases, it might actually run just to collapse a commment, but that's fairly rare) +# it needs `contents: write` in order to add a comment. +# +# `jobs.comment-pr` runs when a pull_request is made to a repository and the `jobs.spelling` job needs to make a comment +# or collapse a comment (in the case where it had previously made a comment and now no longer needs to show a comment) +# it needs `pull-requests: write` in order to manipulate those comments. + +# Updating pull request branches is managed via comment handling. +# For details, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-expect-list +# +# These elements work together to make it happen: +# +# `on.issue_comment` +# This event listens to comments by users asking to update the metadata. +# +# `jobs.update` +# This job runs in response to an issue_comment and will push a new commit +# to update the spelling metadata. +# +# `with.experimental_apply_changes_via_bot` +# Tells the action to support and generate messages that enable it +# to make a commit to update the spelling metadata. +# +# `with.ssh_key` +# In order to trigger workflows when the commit is made, you can provide a +# secret (typically, a write-enabled github deploy key). +# +# For background, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-with-deploy-key + +on: + push: + branches: + - "**" + tags-ignore: + - "**" + pull_request_target: + branches: + - "**" + tags-ignore: + - "**" + types: + - 'opened' + - 'reopened' + - 'synchronize' + issue_comment: + types: + - 'created' + +jobs: + spelling: + name: Spell checking + permissions: + contents: read + pull-requests: read + actions: read + outputs: + followup: ${{ steps.spelling.outputs.followup }} + runs-on: ubuntu-latest + if: "contains(github.event_name, 'pull_request') || github.event_name == 'push'" + concurrency: + group: spelling-${{ github.event.pull_request.number || github.ref }} + # note: If you use only_check_changed_files, you do not want cancel-in-progress + cancel-in-progress: true + steps: + - name: check-spelling + id: spelling + uses: check-spelling/check-spelling@v0.0.21 + with: + suppress_push_for_open_pull_request: 1 + checkout: true + check_file_names: 1 + spell_check_this: check-spelling/spell-check-this@prerelease + post_comment: 0 + use_magic_file: 1 + extra_dictionary_limit: 10 + extra_dictionaries: + cspell:software-terms/src/software-terms.txt + cspell:python/src/python/python-lib.txt + cspell:node/node.txt + cspell:cpp/src/stdlib-c.txt + cspell:cpp/src/stdlib-cpp.txt + cspell:fullstack/fullstack.txt + cspell:filetypes/filetypes.txt + cspell:html/html.txt + cspell:cpp/src/compiler-msvc.txt + cspell:python/src/common/extra.txt + cspell:powershell/powershell.txt + cspell:aws/aws.txt + cspell:cpp/src/lang-keywords.txt + cspell:npm/npm.txt + cspell:dotnet/dotnet.txt + cspell:python/src/python/python.txt + cspell:css/css.txt + cspell:cpp/src/stdlib-cmath.txt + check_extra_dictionaries: '' + + comment-push: + name: Report (Push) + # If your workflow isn't running on push, you can remove this job + runs-on: ubuntu-latest + needs: spelling + permissions: + contents: write + if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push' + steps: + - name: comment + uses: check-spelling/check-spelling@v0.0.21 + with: + checkout: true + spell_check_this: check-spelling/spell-check-this@prerelease + task: ${{ needs.spelling.outputs.followup }} + + comment-pr: + name: Report (PR) + # If you workflow isn't running on pull_request*, you can remove this job + runs-on: ubuntu-latest + needs: spelling + permissions: + pull-requests: write + if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request') + steps: + - name: comment + uses: check-spelling/check-spelling@v0.0.21 + with: + checkout: true + spell_check_this: check-spelling/spell-check-this@prerelease + task: ${{ needs.spelling.outputs.followup }} diff --git a/src/cascadia/TerminalApp/IPane.idl b/src/cascadia/TerminalApp/IPane.idl new file mode 100644 index 00000000000..8697849c926 --- /dev/null +++ b/src/cascadia/TerminalApp/IPane.idl @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "LeafPane.idl"; + +namespace TerminalApp +{ + struct SnapSizeResult + { + Single lower; + Single higher; + }; + + interface IPane + { + void FocusPane(UInt32 idNum); + void FocusFirstChild(); + UInt32 GetLeafPaneCount(); + IPane GetActivePane(); + IPane FindFirstLeaf(); + Boolean HasFocusedChild(); + + Boolean ContainsReadOnly(); + + Windows.UI.Xaml.Controls.Grid GetRootElement(); + + void Shutdown(); + void ClearActive(); + void UpdateSettings(Microsoft.Terminal.Settings.Model.TerminalSettingsCreateResult settings, Guid profile); + + void ResizeContent(Windows.Foundation.Size newSize); + Single CalcSnappedDimensionSingle(Boolean widthOrHeight, Single dimension); + Windows.Foundation.Size GetMinSize(); + + void Maximize(IPane paneToZoom); + void Restore(IPane paneToUnzoom); + + Windows.Foundation.IReference PreCalculateAutoSplit(IPane target, Windows.Foundation.Size parentSize); + Windows.Foundation.IReference PreCalculateCanSplit(IPane target, Microsoft.Terminal.Settings.Model.SplitState splitType, Single splitSize, Windows.Foundation.Size availableSpace); + + SnapSizeResult CalcSnappedDimension(Boolean widthOrHeight, Single fullSize); + + event Windows.Foundation.TypedEventHandler PaneTypeChanged; + } +} diff --git a/src/cascadia/TerminalApp/LeafPane.cpp b/src/cascadia/TerminalApp/LeafPane.cpp new file mode 100644 index 00000000000..449f9c09525 --- /dev/null +++ b/src/cascadia/TerminalApp/LeafPane.cpp @@ -0,0 +1,699 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "LeafPane.h" +#include "AppLogic.h" + +#include + +#include "LeafPane.g.cpp" + +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Graphics::Display; +using namespace winrt::Windows::UI; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Core; +using namespace winrt::Windows::UI::Xaml::Media; +using namespace winrt::Microsoft::Terminal::Settings::Model; +using namespace winrt::Microsoft::Terminal::Control; +using namespace winrt::Microsoft::Terminal::TerminalConnection; +using namespace TerminalApp; + +namespace winrt::TerminalApp::implementation +{ + winrt::Windows::UI::Xaml::Media::SolidColorBrush LeafPane::s_focusedBorderBrush = { nullptr }; + winrt::Windows::UI::Xaml::Media::SolidColorBrush LeafPane::s_unfocusedBorderBrush = { nullptr }; + + static constexpr float Half = 0.50f; + static const int PaneBorderSize = 2; + static const int CombinedPaneBorderSize = 2 * PaneBorderSize; + + LeafPane::LeafPane() + { + InitializeComponent(); + } + + LeafPane::LeafPane(const GUID& profile, const TermControl& control, const bool lastFocused) : + _control{ control }, + _lastActive{ lastFocused }, + _profile{ profile } + { + InitializeComponent(); + GridBorder().Child(_control); + + _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &LeafPane::_ControlConnectionStateChangedHandler }); + _warningBellToken = _control.WarningBell({ this, &LeafPane::_ControlWarningBellHandler }); + + // On the Pane's creation, lookup resources we'll use to theme the + // Pane, including the brushed to use for the focused/unfocused border + // color. + _SetupResources(); + + // Use the unfocused border color as the pane background, so an actual color + // appears behind panes as we animate them sliding in. + Root().Background(s_unfocusedBorderBrush); + + // Register an event with the control to have it inform us when it gains focus. + _gotFocusRevoker = control.GotFocus(winrt::auto_revoke, { this, &LeafPane::_ControlGotFocusHandler }); + _lostFocusRevoker = control.LostFocus(winrt::auto_revoke, { this, &LeafPane::_ControlLostFocusHandler }); + } + + void LeafPane::BorderTappedHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/, + winrt::Windows::UI::Xaml::Input::TappedRoutedEventArgs const& e) + { + FocusFirstChild(); + e.Handled(true); + } + + // Method Description: + // - If this is the last focused pane, returns itself + // - This Pane's control might not currently be focused, if the tab itself is + // not currently focused + // Return Value: + // - nullptr if we're unfocused, else returns this + IPane LeafPane::GetActivePane() + { + return _lastActive ? IPane{ *this } : nullptr; + } + + IPane LeafPane::FindFirstLeaf() + { + return IPane{ *this }; + } + + // Method Description: + // - Gets the TermControl of this pane + // Return Value: + // - The TermControl of this LeafPane + TermControl LeafPane::TerminalControl() + { + return _control; + } + + GUID LeafPane::Profile() + { + return _profile; + } + + // Method Description: + // - Get the root UIElement of this pane, which in our case just contains a border + // with a Terminal Control in it + // Return Value: + // - the Grid acting as the root of this pane. + Controls::Grid LeafPane::GetRootElement() + { + return Root(); + } + + // Method Description: + // - Returns true if this pane was the last pane to be focused in a tree of panes. + // Return Value: + // - true iff we were the last pane focused in this tree of panes. + bool LeafPane::WasLastFocused() const noexcept + { + return _lastActive; + } + + // Method Description: + // - Update the focus state of this pane. We'll make sure to colorize our + // borders depending on if we are the active pane or not. + void LeafPane::UpdateVisuals() + { + GridBorder().BorderBrush(_lastActive ? s_focusedBorderBrush : s_unfocusedBorderBrush); + } + + // Method Description: + // - Remove the "Active" state from this Pane + // - Updates our visuals to match our new state, including highlighting our borders + void LeafPane::ClearActive() + { + _lastActive = false; + UpdateVisuals(); + } + + // Method Description: + // - Sets the "Active" state on this Pane. Only one Pane in a tree of Panes + // should be "active" + // - Updates our visuals to match our new state, including highlighting our borders. + void LeafPane::SetActive() + { + _lastActive = true; + UpdateVisuals(); + } + + // Method Description: + // - Updates the settings of this pane if our profile guid matches the parameter + // Arguments: + // - settings: The new TerminalSettings to apply to any matching controls + // - profile: The GUID of the profile these settings should apply to. + void LeafPane::UpdateSettings(const TerminalSettingsCreateResult& settings, const GUID& profile) + { + if (profile == _profile) + { + auto controlSettings = _control.Settings().as(); + // Update the parent of the control's settings object (and not the object itself) so + // that any overrides made by the control don't get affected by the reload + controlSettings.SetParent(settings.DefaultSettings()); + auto unfocusedSettings{ settings.UnfocusedSettings() }; + if (unfocusedSettings) + { + // Note: the unfocused settings needs to be entirely unchanged _except_ we need to + // set its parent to the settings object that lives in the control. This is because + // the overrides made by the control live in that settings object, so we want to make + // sure the unfocused settings inherit from that. + unfocusedSettings.SetParent(controlSettings); + } + _control.UnfocusedAppearance(unfocusedSettings); + _control.UpdateSettings(); + } + } + + // Method Description: + // - Focuses this pane if the given id matches ours + // Arguments: + // - The ID of the pane we want to focus + void LeafPane::FocusPane(uint32_t id) + { + if (_Id == id) + { + _control.Focus(FocusState::Programmatic); + } + } + + // Method Description: + // - Focuses this control + void LeafPane::FocusFirstChild() + { + if (Root().ActualWidth() == 0 && Root().ActualHeight() == 0) + { + // When these sizes are 0, then the pane might still be in startup, + // and doesn't yet have a real size. In that case, the control.Focus + // event won't be handled until _after_ the startup events are all + // processed. This will lead to the Tab not being notified that the + // focus moved to a different Pane. + // + // In that scenario, trigger the event manually here, to correctly + // inform the Tab that we're now focused. + _GotFocusHandlers(*this); + } + + _control.Focus(FocusState::Programmatic); + } + + // Method Description: + // - Returns true if this pane is currently focused + // Return Value: + // - true if the currently focused pane is this pane + bool LeafPane::HasFocusedChild() + { + return _control && _lastActive; + } + + bool LeafPane::ContainsReadOnly() + { + return _control.ReadOnly(); + } + + // Method Description: + // - Splits this pane, creating a new leaf pane and a parent pane + // - The parent pane holds this pane and the newly created neighbour + // - Emits an event with the new parent, so that whoever is listening + // will replace us with our parent + // Arguments: + // - splitType: what type of split we want to create. + // - profile: The profile GUID to associate with the newly created pane. + // - control: A TermControl to use in the new pane. + // Return Value: + // - The newly created pane + TerminalApp::LeafPane LeafPane::Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType, + const float splitSize, + const GUID& profile, + const winrt::Microsoft::Terminal::Control::TermControl& control) + { + splitType = _convertAutomaticSplitState(splitType); + auto newNeighbour{ make(profile, control, false) }; + + // Update the border of this pane and set appropriate border for the new leaf pane. + if (splitType == SplitState::Vertical) + { + newNeighbour.Borders(_Borders | BordersEnum::Left); + _Borders = _Borders | BordersEnum::Right; + } + else + { + newNeighbour.Borders(_Borders | BordersEnum::Top); + _Borders = _Borders | BordersEnum::Bottom; + } + + UpdateBorders(); + newNeighbour.UpdateBorders(); + + if (WasLastFocused()) + { + ClearActive(); + newNeighbour.SetActive(); + } + + const auto newParent{ make(*this, newNeighbour, splitType, 1.0f - splitSize) }; + + _PaneTypeChangedHandlers(nullptr, newParent); + + newParent.InitializeChildren(); + + return newNeighbour; + } + + // Method Description: + // - Adjusts given dimension (width or height) so that we align with our character grid + // as close as possible. Snaps to closes match (either upward or downward). Also makes + // sure to fit in minimal sizes of the panes. + // Arguments: + // - widthOrHeight: if true operates on width, otherwise on height + // - dimension: a dimension (width or height) to snap + // Return Value: + // - A value corresponding to the next closest snap size for this Pane, either upward or downward + float LeafPane::CalcSnappedDimensionSingle(const bool widthOrHeight, const float dimension) const + { + const auto [lower, higher] = CalcSnappedDimension(widthOrHeight, dimension); + return dimension - lower < higher - dimension ? lower : higher; + } + + // Method Description: + // - Closes our attached control, preparing us to be removed from the UI tree + void LeafPane::Shutdown() + { + _control.Close(); + } + + // Method Description: + // - Fire our Closed event to tell our parent that we should be removed. + void LeafPane::Close() + { + _ClosedHandlers(*this, nullptr); + } + + uint32_t LeafPane::GetLeafPaneCount() const noexcept + { + return 1; + } + + // Method Description: + // - If this is the pane the caller wishes to zoom, we set our zoomed flag + // and update our borders + // Arguments: + // - zoomedPane: This is the pane which we're attempting to zoom on. + void LeafPane::Maximize(IPane paneToZoom) + { + _zoomed = (paneToZoom == *this); + UpdateBorders(); + } + + void LeafPane::Restore(IPane /*paneToUnzoom*/) + { + _zoomed = false; + UpdateBorders(); + } + + // Method Description: + // - Get the absolute minimum size that this pane can be resized to and still + // have 1x1 character visible. Since we're a leaf, we'll + // include the space needed for borders _within_ us. + // Return Value: + // - The minimum size that this pane can be resized to and still have a visible + // character. + Size LeafPane::GetMinSize() const + { + auto controlSize = _control.MinimumSize(); + auto newWidth = controlSize.Width; + auto newHeight = controlSize.Height; + + newWidth += WI_IsFlagSet(_Borders, BordersEnum::Left) ? PaneBorderSize : 0; + newWidth += WI_IsFlagSet(_Borders, BordersEnum::Right) ? PaneBorderSize : 0; + newHeight += WI_IsFlagSet(_Borders, BordersEnum::Top) ? PaneBorderSize : 0; + newHeight += WI_IsFlagSet(_Borders, BordersEnum::Bottom) ? PaneBorderSize : 0; + + return { newWidth, newHeight }; + } + + // Method Description: + // - This is a helper to determine which direction an "Automatic" split should + // happen in for a given pane, but without using the ActualWidth() and + // ActualHeight() methods. This is used during the initialization of the + // Terminal, when we could be processing many "split-pane" commands _before_ + // we've ever laid out the Terminal for the first time. When this happens, the + // Pane's don't have an actual size yet. However, we'd still like to figure + // out how to do an "auto" split when these Panes are all laid out. + // - This method assumes that the Pane we're attempting to split is `target`, + // and this method should be called on the root of a tree of Panes. + // - We'll walk down the tree attempting to find `target`. As we traverse the + // tree, we'll reduce the size passed to each subsequent recursive call. The + // size passed to this method represents how much space this Pane _will_ have + // to use. + // * If this pane is the pane we're looking for, use the + // available space to calculate which direction to split in. + // * If this pane is _any other leaf_, then just return nullopt, to indicate + // that the `target` Pane is not down this branch. + // Arguments: + // - target: The Pane we're attempting to split. + // - availableSpace: The theoretical space that's available for this pane to be able to split. + // Return Value: + // - nullopt if `target` is not this pane, otherwise the + // SplitState that `target` would use for an `Automatic` split given + // `availableSpace` + IReference LeafPane::PreCalculateAutoSplit(const IPane target, + const winrt::Windows::Foundation::Size availableSpace) const + { + if (winrt::get_self(target) == this) + { + // Use the available space to calculate which direction to split in. + return availableSpace.Width > availableSpace.Height ? SplitState::Vertical : SplitState::Horizontal; + } + else + { + // If this pane is _any other leaf_, then just return nullptr, to + // indicate that the `target` Pane is not down this branch. + return nullptr; + } + } + + // Method Description: + // - This is a helper to determine if a given Pane can be split, but without + // using the ActualWidth() and ActualHeight() methods. This is used during + // processing of many "split-pane" commands, which could happen _before_ we've + // laid out a Pane for the first time. When this happens, the Pane's don't + // have an actual size yet. However, we'd still like to figure out if the pane + // could be split, once they're all laid out. + // - This method assumes that the Pane we're attempting to split is `target`, + // and this method should be called on the root of a tree of Panes. + // - We'll walk down the tree attempting to find `target`. As we traverse the + // tree, we'll reduce the size passed to each subsequent recursive call. The + // size passed to this method represents how much space this Pane _will_ have + // to use. + // - If this pane is the pane we're looking for, use the + // available space to calculate which direction to split in. + // - If this pane is _any other leaf_, then just return nullopt, to indicate + // that the `target` Pane is not down this branch. + // Arguments: + // - target: The Pane we're attempting to split. + // - splitType: The direction we're attempting to split in. + // - availableSpace: The theoretical space that's available for this pane to be able to split. + // Return Value: + // - nullopt if `target` is not this pane, otherwise + // true iff we could split this pane, given `availableSpace` + // Note: + // - This method is highly similar to Pane::PreCalculateAutoSplit + IReference LeafPane::PreCalculateCanSplit(const IPane target, + SplitState splitType, + const float splitSize, + const winrt::Windows::Foundation::Size availableSpace) const + { + if (winrt::get_self(target) == this) + { + const auto firstPrecent = 1.0f - splitSize; + const auto secondPercent = splitSize; + // If this pane is a leaf, and it's the pane we're looking for, use + // the available space to calculate which direction to split in. + const Size minSize = GetMinSize(); + + if (splitType == SplitState::None) + { + return { false }; + } + else if (splitType == SplitState::Vertical) + { + const auto widthMinusSeparator = availableSpace.Width - CombinedPaneBorderSize; + const auto newFirstWidth = widthMinusSeparator * firstPrecent; + const auto newSecondWidth = widthMinusSeparator * secondPercent; + + return { newFirstWidth > minSize.Width && newSecondWidth > minSize.Width }; + } + else if (splitType == SplitState::Horizontal) + { + const auto heightMinusSeparator = availableSpace.Height - CombinedPaneBorderSize; + const auto newFirstHeight = heightMinusSeparator * firstPrecent; + const auto newSecondHeight = heightMinusSeparator * secondPercent; + + return { newFirstHeight > minSize.Height && newSecondHeight > minSize.Height }; + } + + return nullptr; + } + else + { + // If this pane is _any other leaf_, then just return nullptr, to + // indicate that the `target` Pane is not down this branch. + return nullptr; + } + } + + void LeafPane::UpdateBorderWithClosedNeighbor(TerminalApp::LeafPane closedNeighbor, const ResizeDirection& neighborDirection) + { + // Set the border on the side we shared to the same state that the neighbour had. + switch (neighborDirection) + { + case ResizeDirection::Up: + WI_UpdateFlag(_Borders, BordersEnum::Top, WI_IsFlagSet(closedNeighbor.Borders(), BordersEnum::Top)); + break; + case ResizeDirection::Down: + WI_UpdateFlag(_Borders, BordersEnum::Bottom, WI_IsFlagSet(closedNeighbor.Borders(), BordersEnum::Bottom)); + break; + case ResizeDirection::Left: + WI_UpdateFlag(_Borders, BordersEnum::Left, WI_IsFlagSet(closedNeighbor.Borders(), BordersEnum::Left)); + break; + case ResizeDirection::Right: + WI_UpdateFlag(_Borders, BordersEnum::Right, WI_IsFlagSet(closedNeighbor.Borders(), BordersEnum::Right)); + break; + } + + UpdateBorders(); + } + + // Method Description: + // - Called when our attached control is closed. Triggers listeners to our close event + void LeafPane::_ControlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Windows::Foundation::IInspectable& /*args*/) + { + const auto newConnectionState = _control.ConnectionState(); + const auto previousConnectionState = std::exchange(_connectionState, newConnectionState); + + if (newConnectionState < ConnectionState::Closed) + { + // Pane doesn't care if the connection isn't entering a terminal state. + return; + } + + if (previousConnectionState < ConnectionState::Connected && newConnectionState >= ConnectionState::Failed) + { + // A failure to complete the connection (before it has _connected_) is not covered by "closeOnExit". + // This is to prevent a misconfiguration (closeOnExit: always, startingDirectory: garbage) resulting + // in Terminal flashing open and immediately closed. + return; + } + + const auto settings{ winrt::TerminalApp::implementation::AppLogic::CurrentAppSettings() }; + auto paneProfile = settings.FindProfile(_profile); + if (paneProfile) + { + auto mode = paneProfile.CloseOnExit(); + if ((mode == CloseOnExitMode::Always) || + (mode == CloseOnExitMode::Graceful && newConnectionState == ConnectionState::Closed)) + { + Close(); + } + } + } + + // Method Description: + // - Plays a warning note when triggered by the BEL control character, + // using the sound configured for the "Critical Stop" system event.` + // This matches the behavior of the Windows Console host. + // - Will also flash the taskbar if the bellStyle setting for this profile + // has the 'visual' flag set + void LeafPane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Windows::Foundation::IInspectable& /*eventArgs*/) + { + const auto settings{ winrt::TerminalApp::implementation::AppLogic::CurrentAppSettings() }; + auto paneProfile = settings.FindProfile(_profile); + if (paneProfile) + { + if (WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Audible)) + { + const auto soundAlias = reinterpret_cast(SND_ALIAS_SYSTEMHAND); + PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY); + } + if (WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Window)) + { + _control.BellLightOn(); + } + // Bubble this event up to app host, starting with bubbling to the hosting tab + _PaneRaiseBellHandlers(nullptr, WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Taskbar)); + } + } + + // Event Description: + // - Called when our control gains focus. We'll use this to trigger our GotFocus + // callback. The tab that's hosting us should have registered a callback which + // can be used to mark us as active. + void LeafPane::_ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& /* sender */, + RoutedEventArgs const& /* args */) + { + _GotFocusHandlers(*this); + } + + void LeafPane::_ControlLostFocusHandler(winrt::Windows::Foundation::IInspectable const& /* sender */, + RoutedEventArgs const& /* args */) + { + _LostFocusHandlers(*this); + } + + // Method Description: + // - Sets the thickness of each side of our borders to match our _borders state. + void LeafPane::UpdateBorders() + { + double top = 0, bottom = 0, left = 0, right = 0; + + Thickness newBorders{ 0 }; + if (_zoomed) + { + // When the pane is zoomed, manually show all the borders around the window. + top = bottom = right = left = PaneBorderSize; + } + else + { + if (WI_IsFlagSet(_Borders, BordersEnum::Top)) + { + top = PaneBorderSize; + } + if (WI_IsFlagSet(_Borders, BordersEnum::Bottom)) + { + bottom = PaneBorderSize; + } + if (WI_IsFlagSet(_Borders, BordersEnum::Left)) + { + left = PaneBorderSize; + } + if (WI_IsFlagSet(_Borders, BordersEnum::Right)) + { + right = PaneBorderSize; + } + } + GridBorder().BorderThickness(ThicknessHelper::FromLengths(left, top, right, bottom)); + } + + // Function Description: + // - Attempts to load some XAML resources that the Pane will need. This includes: + // * The Color we'll use for active Panes's borders - SystemAccentColor + // * The Brush we'll use for inactive Panes - TabViewBackground (to match the + // color of the titlebar) + void LeafPane::_SetupResources() + { + const auto res = Application::Current().Resources(); + const auto accentColorKey = winrt::box_value(L"SystemAccentColor"); + if (res.HasKey(accentColorKey)) + { + const auto colorFromResources = res.Lookup(accentColorKey); + // If SystemAccentColor is _not_ a Color for some reason, use + // Transparent as the color, so we don't do this process again on + // the next pane (by leaving s_focusedBorderBrush nullptr) + auto actualColor = winrt::unbox_value_or(colorFromResources, Colors::Black()); + s_focusedBorderBrush = SolidColorBrush(actualColor); + } + else + { + // DON'T use Transparent here - if it's "Transparent", then it won't + // be able to hittest for clicks, and then clicking on the border + // will eat focus. + s_focusedBorderBrush = SolidColorBrush{ Colors::Black() }; + } + + const auto tabViewBackgroundKey = winrt::box_value(L"TabViewBackground"); + if (res.HasKey(tabViewBackgroundKey)) + { + winrt::Windows::Foundation::IInspectable obj = res.Lookup(tabViewBackgroundKey); + s_unfocusedBorderBrush = obj.try_as(); + } + else + { + // DON'T use Transparent here - if it's "Transparent", then it won't + // be able to hittest for clicks, and then clicking on the border + // will eat focus. + s_unfocusedBorderBrush = SolidColorBrush{ Colors::Black() }; + } + } + + // Method Description: + // - Adjusts given dimension (width or height) so that we align with our character grid + // as close as possible. Also makes sure to fit in minimal sizes of the pane + // Arguments: + // - widthOrHeight: if true operates on width, otherwise on height + // - dimension: a dimension (width or height) to be snapped + // Return Value: + // - pair of floats, where first value is the size snapped downward (not greater then + // requested size) and second is the size snapped upward (not lower than requested size). + // If requested size is already snapped, then both returned values equal this value. + SnapSizeResult LeafPane::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const + { + const auto minSize = GetMinSize(); + const auto minDimension = widthOrHeight ? minSize.Width : minSize.Height; + + if (dimension <= minDimension) + { + return { minDimension, minDimension }; + } + + float lower = _control.SnapDimensionToGrid(widthOrHeight, dimension); + if (widthOrHeight) + { + lower += WI_IsFlagSet(_Borders, BordersEnum::Left) ? PaneBorderSize : 0; + lower += WI_IsFlagSet(_Borders, BordersEnum::Right) ? PaneBorderSize : 0; + } + else + { + lower += WI_IsFlagSet(_Borders, BordersEnum::Top) ? PaneBorderSize : 0; + lower += WI_IsFlagSet(_Borders, BordersEnum::Bottom) ? PaneBorderSize : 0; + } + + if (lower == dimension) + { + // If we happen to be already snapped, then just return this size + // as both lower and higher values. + return { lower, lower }; + } + else + { + const auto cellSize = _control.CharacterDimensions(); + const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height); + return { lower, higher }; + } + } + + // Method Description: + // - Converts an "automatic" split type into either Vertical or Horizontal, + // based upon the current dimensions of the Pane. + // - If any of the other SplitState values are passed in, they're returned + // unmodified. + // Arguments: + // - splitType: The SplitState to attempt to convert + // Return Value: + // - None if splitType was None, otherwise one of Horizontal or Vertical + SplitState LeafPane::_convertAutomaticSplitState(const SplitState& splitType) + { + // Careful here! If the pane doesn't yet have a size, these dimensions will + // be 0, and we'll always return Vertical. + + if (splitType == SplitState::Automatic) + { + // If the requested split type was "auto", determine which direction to + // split based on our current dimensions + const Size actualSize{ gsl::narrow_cast(Root().ActualWidth()), + gsl::narrow_cast(Root().ActualHeight()) }; + return actualSize.Width >= actualSize.Height ? SplitState::Vertical : SplitState::Horizontal; + } + return splitType; + } + + DEFINE_EVENT(LeafPane, GotFocus, _GotFocusHandlers, winrt::delegate); + DEFINE_EVENT(LeafPane, LostFocus, _LostFocusHandlers, winrt::delegate); + DEFINE_EVENT(LeafPane, PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler); +} diff --git a/src/cascadia/TerminalApp/LeafPane.h b/src/cascadia/TerminalApp/LeafPane.h new file mode 100644 index 00000000000..1a082314427 --- /dev/null +++ b/src/cascadia/TerminalApp/LeafPane.h @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// Module Name: +// - LeafPane.h +// +// Abstract: +// - Panes are an abstraction by which the terminal can display multiple terminal +// instances simultaneously in a single terminal window. While tabs allow for +// a single terminal window to have many terminal sessions running +// simultaneously within a single window, only one tab can be visible at a +// time. Panes, on the other hand, allow a user to have many different +// terminal sessions visible to the user within the context of a single window +// at the same time. This can enable greater productivity from the user, as +// they can see the output of one terminal window while working in another. +// - See doc/cascadia/Panes.md for a detailed description. +// - Panes can be one of 2 types, parent or leaf. A parent pane contains 2 other panes +// (each of which could itself be a parent or could be a leaf). A leaf pane contains +// a terminal control. +// +// Author(s): +// - Mike Griese (zadjii-msft) 16-May-2019 +// - Pankaj Bhojwani February-2021 + +#pragma once + +#include "../../cascadia/inc/cppwinrt_utils.h" + +#include "LeafPane.g.h" + +namespace winrt::TerminalApp::implementation +{ + DEFINE_ENUM_FLAG_OPERATORS(BordersEnum); + + struct LeafPane : LeafPaneT + { + public: + LeafPane(); + LeafPane(const GUID& profile, + const winrt::Microsoft::Terminal::Control::TermControl& control, + const bool lastFocused = false); + + winrt::Windows::UI::Xaml::Controls::Grid GetRootElement(); + + IPane GetActivePane(); + IPane FindFirstLeaf(); + winrt::Microsoft::Terminal::Control::TermControl TerminalControl(); + GUID Profile(); + void FocusPane(uint32_t id); + void FocusFirstChild(); + bool HasFocusedChild(); + + bool ContainsReadOnly(); + + bool WasLastFocused() const noexcept; + void UpdateVisuals(); + void ClearActive(); + void SetActive(); + + void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, + const GUID& profile); + void ResizeContent(const winrt::Windows::Foundation::Size& /*newSize*/){}; + + TerminalApp::LeafPane Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType, + const float splitSize, + const GUID& profile, + const winrt::Microsoft::Terminal::Control::TermControl& control); + + float CalcSnappedDimensionSingle(const bool widthOrHeight, const float dimension) const; + void Shutdown(); + void Close(); + uint32_t GetLeafPaneCount() const noexcept; + + void Maximize(IPane paneToZoom); + void Restore(IPane paneToUnZoom); + + winrt::Windows::Foundation::Size GetMinSize() const; + + winrt::Windows::Foundation::IReference PreCalculateAutoSplit(const IPane target, + const winrt::Windows::Foundation::Size parentSize) const; + winrt::Windows::Foundation::IReference PreCalculateCanSplit(const IPane target, + winrt::Microsoft::Terminal::Settings::Model::SplitState splitType, + const float splitSize, + const winrt::Windows::Foundation::Size availableSpace) const; + + void UpdateBorderWithClosedNeighbor(TerminalApp::LeafPane closedNeighbor, const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& neighborDirection); + + SnapSizeResult CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; + + void BorderTappedHandler(winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::UI::Xaml::Input::TappedRoutedEventArgs const& e); + + void UpdateBorders(); + + DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate); + DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate); + DECLARE_EVENT(PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler); + TYPED_EVENT(Closed, IPane, IPane); + TYPED_EVENT(PaneTypeChanged, IPane, IPane); + + WINRT_PROPERTY(uint32_t, Id); + WINRT_PROPERTY(BordersEnum, Borders, BordersEnum::None); + + private: + winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr }; + winrt::Microsoft::Terminal::TerminalConnection::ConnectionState _connectionState{ winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::NotConnected }; + GUID _profile; + bool _lastActive{ false }; + bool _zoomed{ false }; + + static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_focusedBorderBrush; + static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_unfocusedBorderBrush; + + winrt::event_token _connectionStateChangedToken{ 0 }; + winrt::event_token _warningBellToken{ 0 }; + + winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker; + winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker; + + void _ControlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); + void _ControlWarningBellHandler(winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::Foundation::IInspectable const& e); + void _ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + void _ControlLostFocusHandler(winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + + static void _SetupResources(); + + winrt::Microsoft::Terminal::Settings::Model::SplitState _convertAutomaticSplitState(const winrt::Microsoft::Terminal::Settings::Model::SplitState& splitType); + }; +} + +namespace winrt::TerminalApp::factory_implementation +{ + BASIC_FACTORY(LeafPane); +} diff --git a/src/cascadia/TerminalApp/LeafPane.idl b/src/cascadia/TerminalApp/LeafPane.idl new file mode 100644 index 00000000000..0ffa4e3d92d --- /dev/null +++ b/src/cascadia/TerminalApp/LeafPane.idl @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "IPane.idl"; + +namespace TerminalApp +{ + enum BordersEnum + { + None = 0, + Top = 1, + Bottom = 2, + Left = 4, + Right = 8 + }; + + [default_interface] runtimeclass LeafPane : Windows.UI.Xaml.Controls.UserControl, IPane + { + LeafPane(Guid profile, Microsoft.Terminal.Control.TermControl con, Boolean active); + + UInt32 Id; + BordersEnum Borders; + Microsoft.Terminal.Control.TermControl TerminalControl { get; }; + Guid Profile { get; }; + + Boolean WasLastFocused(); + void SetActive(); + void Close(); + void UpdateBorders(); + void UpdateBorderWithClosedNeighbor(LeafPane closedNeighbor, Microsoft.Terminal.Settings.Model.ResizeDirection neighborDirection); + LeafPane Split(Microsoft.Terminal.Settings.Model.SplitState splitType, Single splitSize, Guid profile, Microsoft.Terminal.Control.TermControl con); + } +} diff --git a/src/cascadia/TerminalApp/LeafPane.xaml b/src/cascadia/TerminalApp/LeafPane.xaml new file mode 100644 index 00000000000..fd79c9398fc --- /dev/null +++ b/src/cascadia/TerminalApp/LeafPane.xaml @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/src/cascadia/TerminalApp/Pane.LayoutSizeNode.cpp b/src/cascadia/TerminalApp/Pane.LayoutSizeNode.cpp index f3cc8647ff1..7324bdae400 100644 --- a/src/cascadia/TerminalApp/Pane.LayoutSizeNode.cpp +++ b/src/cascadia/TerminalApp/Pane.LayoutSizeNode.cpp @@ -2,9 +2,11 @@ // Licensed under the MIT license. #include "pch.h" -#include "Pane.h" +#include "ParentPane.h" -Pane::LayoutSizeNode::LayoutSizeNode(const float minSize) : +using namespace winrt::TerminalApp::implementation; + +ParentPane::LayoutSizeNode::LayoutSizeNode(const float minSize) : size{ minSize }, isMinimumSize{ true }, firstChild{ nullptr }, @@ -14,7 +16,7 @@ Pane::LayoutSizeNode::LayoutSizeNode(const float minSize) : { } -Pane::LayoutSizeNode::LayoutSizeNode(const LayoutSizeNode& other) : +ParentPane::LayoutSizeNode::LayoutSizeNode(const LayoutSizeNode& other) : size{ other.size }, isMinimumSize{ other.isMinimumSize }, firstChild{ other.firstChild ? std::make_unique(*other.firstChild) : nullptr }, @@ -32,7 +34,7 @@ Pane::LayoutSizeNode::LayoutSizeNode(const LayoutSizeNode& other) : // - other: Node to take the values from. // Return Value: // - itself -Pane::LayoutSizeNode& Pane::LayoutSizeNode::operator=(const LayoutSizeNode& other) +ParentPane::LayoutSizeNode& ParentPane::LayoutSizeNode::operator=(const LayoutSizeNode& other) { size = other.size; isMinimumSize = other.isMinimumSize; @@ -53,7 +55,7 @@ Pane::LayoutSizeNode& Pane::LayoutSizeNode::operator=(const LayoutSizeNode& othe // - other: Node to take the values from. // Return Value: // - -void Pane::LayoutSizeNode::_AssignChildNode(std::unique_ptr& nodeField, const LayoutSizeNode* const newNode) +void ParentPane::LayoutSizeNode::_AssignChildNode(std::unique_ptr& nodeField, const LayoutSizeNode* const newNode) { if (newNode) { diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp deleted file mode 100644 index cb8f4278a76..00000000000 --- a/src/cascadia/TerminalApp/Pane.cpp +++ /dev/null @@ -1,2138 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include "Pane.h" -#include "AppLogic.h" - -#include - -using namespace winrt::Windows::Foundation; -using namespace winrt::Windows::Graphics::Display; -using namespace winrt::Windows::UI; -using namespace winrt::Windows::UI::Xaml; -using namespace winrt::Windows::UI::Core; -using namespace winrt::Windows::UI::Xaml::Media; -using namespace winrt::Microsoft::Terminal::Settings::Model; -using namespace winrt::Microsoft::Terminal::Control; -using namespace winrt::Microsoft::Terminal::TerminalConnection; -using namespace winrt::TerminalApp; -using namespace TerminalApp; - -static const int PaneBorderSize = 2; -static const int CombinedPaneBorderSize = 2 * PaneBorderSize; - -// WARNING: Don't do this! This won't work -// Duration duration{ std::chrono::milliseconds{ 200 } }; -// Instead, make a duration from a TimeSpan from the time in millis -// -// 200ms was chosen because it's quick enough that it doesn't break your -// flow, but not too quick to see -static const int AnimationDurationInMilliseconds = 200; -static const Duration AnimationDuration = DurationHelper::FromTimeSpan(winrt::Windows::Foundation::TimeSpan(std::chrono::milliseconds(AnimationDurationInMilliseconds))); - -winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_focusedBorderBrush = { nullptr }; -winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_unfocusedBorderBrush = { nullptr }; - -Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocused) : - _control{ control }, - _lastActive{ lastFocused }, - _profile{ profile } -{ - _root.Children().Append(_border); - _border.Child(_control); - - _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler }); - _warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler }); - - // On the first Pane's creation, lookup resources we'll use to theme the - // Pane, including the brushed to use for the focused/unfocused border - // color. - if (s_focusedBorderBrush == nullptr || s_unfocusedBorderBrush == nullptr) - { - _SetupResources(); - } - - // Use the unfocused border color as the pane background, so an actual color - // appears behind panes as we animate them sliding in. - _root.Background(s_unfocusedBorderBrush); - - // Register an event with the control to have it inform us when it gains focus. - _gotFocusRevoker = control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler }); - _lostFocusRevoker = control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler }); - - // When our border is tapped, make sure to transfer focus to our control. - // LOAD-BEARING: This will NOT work if the border's BorderBrush is set to - // Colors::Transparent! The border won't get Tapped events, and they'll fall - // through to something else. - _border.Tapped([this](auto&, auto& e) { - _FocusFirstChild(); - e.Handled(true); - }); -} - -// Method Description: -// - Update the size of this pane. Resizes each of our columns so they have the -// same relative sizes, given the newSize. -// - Because we're just manually setting the row/column sizes in pixels, we have -// to be told our new size, we can't just use our own OnSized event, because -// that _won't fire when we get smaller_. -// Arguments: -// - newSize: the amount of space that this pane has to fill now. -// Return Value: -// - -void Pane::ResizeContent(const Size& newSize) -{ - const auto width = newSize.Width; - const auto height = newSize.Height; - - _CreateRowColDefinitions(); - - if (_splitState == SplitState::Vertical) - { - const auto paneSizes = _CalcChildrenSizes(width); - - const Size firstSize{ paneSizes.first, height }; - const Size secondSize{ paneSizes.second, height }; - _firstChild->ResizeContent(firstSize); - _secondChild->ResizeContent(secondSize); - } - else if (_splitState == SplitState::Horizontal) - { - const auto paneSizes = _CalcChildrenSizes(height); - - const Size firstSize{ width, paneSizes.first }; - const Size secondSize{ width, paneSizes.second }; - _firstChild->ResizeContent(firstSize); - _secondChild->ResizeContent(secondSize); - } -} - -// Method Description: -// - Recalculates and reapplies sizes of all descendant panes. -// Arguments: -// - -// Return Value: -// - -void Pane::Relayout() -{ - ResizeContent(_root.ActualSize()); -} - -// Method Description: -// - Adjust our child percentages to increase the size of one of our children -// and decrease the size of the other. -// - Adjusts the separation amount by 5% -// - Does nothing if the direction doesn't match our current split direction -// Arguments: -// - direction: the direction to move our separator. If it's down or right, -// we'll be increasing the size of the first of our children. Else, we'll be -// decreasing the size of our first child. -// Return Value: -// - false if we couldn't resize this pane in the given direction, else true. -bool Pane::_Resize(const ResizeDirection& direction) -{ - if (!DirectionMatchesSplit(direction, _splitState)) - { - return false; - } - - float amount = .05f; - if (direction == ResizeDirection::Right || direction == ResizeDirection::Down) - { - amount = -amount; - } - - // Make sure we're not making a pane explode here by resizing it to 0 characters. - const bool changeWidth = _splitState == SplitState::Vertical; - - const Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), - gsl::narrow_cast(_root.ActualHeight()) }; - // actualDimension is the size in DIPs of this pane in the direction we're - // resizing. - const auto actualDimension = changeWidth ? actualSize.Width : actualSize.Height; - - _desiredSplitPosition = _ClampSplitPosition(changeWidth, _desiredSplitPosition - amount, actualDimension); - - // Resize our columns to match the new percentages. - ResizeContent(actualSize); - - return true; -} - -// Method Description: -// - Moves the separator between panes, as to resize each child on either size -// of the separator. Tries to move a separator in the given direction. The -// separator moved is the separator that's closest depth-wise to the -// currently focused pane, that's also in the correct direction to be moved. -// If there isn't such a separator, then this method returns false, as we -// couldn't handle the resize. -// Arguments: -// - direction: The direction to move the separator in. -// Return Value: -// - true if we or a child handled this resize request. -bool Pane::ResizePane(const ResizeDirection& direction) -{ - // If we're a leaf, do nothing. We can't possibly have a descendant with a - // separator the correct direction. - if (_IsLeaf()) - { - return false; - } - - // Check if either our first or second child is the currently focused leaf. - // If it is, and the requested resize direction matches our separator, then - // we're the pane that needs to adjust its separator. - // If our separator is the wrong direction, then we can't handle it. - const bool firstIsFocused = _firstChild->_IsLeaf() && _firstChild->_lastActive; - const bool secondIsFocused = _secondChild->_IsLeaf() && _secondChild->_lastActive; - if (firstIsFocused || secondIsFocused) - { - return _Resize(direction); - } - - // If neither of our children were the focused leaf, then recurse into - // our children and see if they can handle the resize. - // For each child, if it has a focused descendant, try having that child - // handle the resize. - // If the child wasn't able to handle the resize, it's possible that - // there were no descendants with a separator the correct direction. If - // our separator _is_ the correct direction, then we should be the pane - // to resize. Otherwise, just return false, as we couldn't handle it - // either. - if ((!_firstChild->_IsLeaf()) && _firstChild->_HasFocusedChild()) - { - return _firstChild->ResizePane(direction) || _Resize(direction); - } - - if ((!_secondChild->_IsLeaf()) && _secondChild->_HasFocusedChild()) - { - return _secondChild->ResizePane(direction) || _Resize(direction); - } - - return false; -} - -// Method Description: -// - Attempts to handle moving focus to one of our children. If our split -// direction isn't appropriate for the move direction, then we'll return -// false, to try and let our parent handle the move. If our child we'd move -// focus to is already focused, we'll also return false, to again let our -// parent try and handle the focus movement. -// Arguments: -// - direction: The direction to move the focus in. -// Return Value: -// - true if we handled this focus move request. -bool Pane::_NavigateFocus(const FocusDirection& direction) -{ - if (!DirectionMatchesSplit(direction, _splitState)) - { - return false; - } - - const bool focusSecond = (direction == FocusDirection::Right) || (direction == FocusDirection::Down); - - const auto newlyFocusedChild = focusSecond ? _secondChild : _firstChild; - - // If the child we want to move focus to is _already_ focused, return false, - // to try and let our parent figure it out. - if (newlyFocusedChild->_HasFocusedChild()) - { - return false; - } - - // Transfer focus to our child, and update the focus of our tree. - newlyFocusedChild->_FocusFirstChild(); - UpdateVisuals(); - - return true; -} - -// Method Description: -// - Attempts to move focus to one of our children. If we have a focused child, -// we'll try to move the focus in the direction requested. -// - If there isn't a pane that exists as a child of this pane in the correct -// direction, we'll return false. This will indicate to our parent that they -// should try and move the focus themselves. In this way, the focus can move -// up and down the tree to the correct pane. -// - This method is _very_ similar to ResizePane. Both are trying to find the -// right separator to move (focus) in a direction. -// Arguments: -// - direction: The direction to move the focus in. -// Return Value: -// - true if we or a child handled this focus move request. -bool Pane::NavigateFocus(const FocusDirection& direction) -{ - // If we're a leaf, do nothing. We can't possibly have a descendant with a - // separator the correct direction. - if (_IsLeaf()) - { - return false; - } - - // Check if either our first or second child is the currently focused leaf. - // If it is, and the requested move direction matches our separator, then - // we're the pane that needs to handle this focus move. - const bool firstIsFocused = _firstChild->_IsLeaf() && _firstChild->_lastActive; - const bool secondIsFocused = _secondChild->_IsLeaf() && _secondChild->_lastActive; - if (firstIsFocused || secondIsFocused) - { - return _NavigateFocus(direction); - } - - // If neither of our children were the focused leaf, then recurse into - // our children and see if they can handle the focus move. - // For each child, if it has a focused descendant, try having that child - // handle the focus move. - // If the child wasn't able to handle the focus move, it's possible that - // there were no descendants with a separator the correct direction. If - // our separator _is_ the correct direction, then we should be the pane - // to move focus into our other child. Otherwise, just return false, as - // we couldn't handle it either. - if ((!_firstChild->_IsLeaf()) && _firstChild->_HasFocusedChild()) - { - return _firstChild->NavigateFocus(direction) || _NavigateFocus(direction); - } - - if ((!_secondChild->_IsLeaf()) && _secondChild->_HasFocusedChild()) - { - return _secondChild->NavigateFocus(direction) || _NavigateFocus(direction); - } - - return false; -} - -// Method Description: -// - Called when our attached control is closed. Triggers listeners to our close -// event, if we're a leaf pane. -// - If this was called, and we became a parent pane (due to work on another -// thread), this function will do nothing (allowing the control's new parent -// to handle the event instead). -// Arguments: -// - -// Return Value: -// - -void Pane::_ControlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, - const winrt::Windows::Foundation::IInspectable& /*args*/) -{ - std::unique_lock lock{ _createCloseLock }; - // It's possible that this event handler started being executed, then before - // we got the lock, another thread created another child. So our control is - // actually no longer _our_ control, and instead could be a descendant. - // - // When the control's new Pane takes ownership of the control, the new - // parent will register it's own event handler. That event handler will get - // fired after this handler returns, and will properly cleanup state. - if (!_IsLeaf()) - { - return; - } - - const auto newConnectionState = _control.ConnectionState(); - const auto previousConnectionState = std::exchange(_connectionState, newConnectionState); - - if (newConnectionState < ConnectionState::Closed) - { - // Pane doesn't care if the connection isn't entering a terminal state. - return; - } - - if (previousConnectionState < ConnectionState::Connected && newConnectionState >= ConnectionState::Failed) - { - // A failure to complete the connection (before it has _connected_) is not covered by "closeOnExit". - // This is to prevent a misconfiguration (closeOnExit: always, startingDirectory: garbage) resulting - // in Terminal flashing open and immediately closed. - return; - } - - const auto settings{ winrt::TerminalApp::implementation::AppLogic::CurrentAppSettings() }; - auto paneProfile = settings.FindProfile(_profile.value()); - if (paneProfile) - { - auto mode = paneProfile.CloseOnExit(); - if ((mode == CloseOnExitMode::Always) || - (mode == CloseOnExitMode::Graceful && newConnectionState == ConnectionState::Closed)) - { - Close(); - } - } -} - -// Method Description: -// - Plays a warning note when triggered by the BEL control character, -// using the sound configured for the "Critical Stop" system event.` -// This matches the behavior of the Windows Console host. -// - Will also flash the taskbar if the bellStyle setting for this profile -// has the 'visual' flag set -// Arguments: -// - -void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, - const winrt::Windows::Foundation::IInspectable& /*eventArgs*/) -{ - if (!_IsLeaf()) - { - return; - } - const auto settings{ winrt::TerminalApp::implementation::AppLogic::CurrentAppSettings() }; - auto paneProfile = settings.FindProfile(_profile.value()); - if (paneProfile) - { - // We don't want to do anything if nothing is set, so check for that first - if (static_cast(paneProfile.BellStyle()) != 0) - { - if (WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Audible)) - { - // Audible is set, play the sound - const auto soundAlias = reinterpret_cast(SND_ALIAS_SYSTEMHAND); - PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY); - } - - if (WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Window)) - { - _control.BellLightOn(); - } - - // raise the event with the bool value corresponding to the taskbar flag - _PaneRaiseBellHandlers(nullptr, WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Taskbar)); - } - } -} - -// Event Description: -// - Called when our control gains focus. We'll use this to trigger our GotFocus -// callback. The tab that's hosting us should have registered a callback which -// can be used to mark us as active. -// Arguments: -// - -// Return Value: -// - -void Pane::_ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& /* sender */, - RoutedEventArgs const& /* args */) -{ - _GotFocusHandlers(shared_from_this()); -} - -// Event Description: -// - Called when our control loses focus. We'll use this to trigger our LostFocus -// callback. The tab that's hosting us should have registered a callback which -// can be used to update its own internal focus state -void Pane::_ControlLostFocusHandler(winrt::Windows::Foundation::IInspectable const& /* sender */, - RoutedEventArgs const& /* args */) -{ - _LostFocusHandlers(shared_from_this()); -} - -// Method Description: -// - Fire our Closed event to tell our parent that we should be removed. -// Arguments: -// - -// Return Value: -// - -void Pane::Close() -{ - // Fire our Closed event to tell our parent that we should be removed. - _ClosedHandlers(nullptr, nullptr); -} - -// Method Description: -// - Prepare this pane to be removed from the UI hierarchy by closing all controls -// and connections beneath it. -void Pane::Shutdown() -{ - // Lock the create/close lock so that another operation won't concurrently - // modify our tree - std::unique_lock lock{ _createCloseLock }; - if (_IsLeaf()) - { - _control.Close(); - } - else - { - _firstChild->Shutdown(); - _secondChild->Shutdown(); - } -} - -// Method Description: -// - Get the root UIElement of this pane. There may be a single TermControl as a -// child, or an entire tree of grids and panes as children of this element. -// Arguments: -// - -// Return Value: -// - the Grid acting as the root of this pane. -Controls::Grid Pane::GetRootElement() -{ - return _root; -} - -// Method Description: -// - If this is the last focused pane, returns itself. Returns nullptr if this -// is a leaf and it's not focused. If it's a parent, it returns nullptr if no -// children of this pane were the last pane to be focused, or the Pane that -// _was_ the last pane to be focused (if there was one). -// - This Pane's control might not currently be focused, if the tab itself is -// not currently focused. -// Return Value: -// - nullptr if we're a leaf and unfocused, or no children were marked -// `_lastActive`, else returns this -std::shared_ptr Pane::GetActivePane() -{ - if (_IsLeaf()) - { - return _lastActive ? shared_from_this() : nullptr; - } - - auto firstFocused = _firstChild->GetActivePane(); - if (firstFocused != nullptr) - { - return firstFocused; - } - return _secondChild->GetActivePane(); -} - -// Method Description: -// - Gets the TermControl of this pane. If this Pane is not a leaf, this will return nullptr. -// Arguments: -// - -// Return Value: -// - nullptr if this Pane is a parent, otherwise the TermControl of this Pane. -TermControl Pane::GetTerminalControl() -{ - return _IsLeaf() ? _control : nullptr; -} - -// Method Description: -// - Recursively remove the "Active" state from this Pane and all it's children. -// - Updates our visuals to match our new state, including highlighting our borders. -// Arguments: -// - -// Return Value: -// - -void Pane::ClearActive() -{ - _lastActive = false; - if (!_IsLeaf()) - { - _firstChild->ClearActive(); - _secondChild->ClearActive(); - } - UpdateVisuals(); -} - -// Method Description: -// - Sets the "Active" state on this Pane. Only one Pane in a tree of Panes -// should be "active", and that pane should be a leaf. -// - Updates our visuals to match our new state, including highlighting our borders. -// Arguments: -// - -// Return Value: -// - -void Pane::SetActive() -{ - _lastActive = true; - UpdateVisuals(); -} - -// Method Description: -// - Returns nullopt if no children of this pane were the last control to be -// focused, or the GUID of the profile of the last control to be focused (if -// there was one). -// Arguments: -// - -// Return Value: -// - nullopt if no children of this pane were the last control to be -// focused, else the GUID of the profile of the last control to be focused -std::optional Pane::GetFocusedProfile() -{ - auto lastFocused = GetActivePane(); - return lastFocused ? lastFocused->_profile : std::nullopt; -} - -// Method Description: -// - Returns true if this pane was the last pane to be focused in a tree of panes. -// Arguments: -// - -// Return Value: -// - true iff we were the last pane focused in this tree of panes. -bool Pane::WasLastFocused() const noexcept -{ - return _lastActive; -} - -// Method Description: -// - Returns true iff this pane has no child panes. -// Arguments: -// - -// Return Value: -// - true iff this pane has no child panes. -bool Pane::_IsLeaf() const noexcept -{ - return _splitState == SplitState::None; -} - -// Method Description: -// - Returns true if this pane is currently focused, or there is a pane which is -// a child of this pane that is actively focused -// Arguments: -// - -// Return Value: -// - true if the currently focused pane is either this pane, or one of this -// pane's descendants -bool Pane::_HasFocusedChild() const noexcept -{ - // We're intentionally making this one giant expression, so the compiler - // will skip the following lookups if one of the lookups before it returns - // true - return (_control && _lastActive) || - (_firstChild && _firstChild->_HasFocusedChild()) || - (_secondChild && _secondChild->_HasFocusedChild()); -} - -// Method Description: -// - Update the focus state of this pane. We'll make sure to colorize our -// borders depending on if we are the active pane or not. -// Arguments: -// - -// Return Value: -// - -void Pane::UpdateVisuals() -{ - _border.BorderBrush(_lastActive ? s_focusedBorderBrush : s_unfocusedBorderBrush); -} - -// Method Description: -// - Focuses this control if we're a leaf, or attempts to focus the first leaf -// of our first child, recursively. -// Arguments: -// - -// Return Value: -// - -void Pane::_FocusFirstChild() -{ - if (_IsLeaf()) - { - // Originally, we would only raise a GotFocus event here when: - // - // if (_root.ActualWidth() == 0 && _root.ActualHeight() == 0) - // - // When these sizes are 0, then the pane might still be in startup, - // and doesn't yet have a real size. In that case, the control.Focus - // event won't be handled until _after_ the startup events are all - // processed. This will lead to the Tab not being notified that the - // focus moved to a different Pane. - // - // However, with the ability to execute multiple actions at a time, in - // already existing windows, we need to always raise this event manually - // here, to correctly inform the Tab that we're now focused. This will - // take care of commandlines like: - // - // `wtd -w 0 mf down ; sp` - // `wtd -w 0 fp -t 1 ; sp` - - _GotFocusHandlers(shared_from_this()); - - _control.Focus(FocusState::Programmatic); - } - else - { - _firstChild->_FocusFirstChild(); - } -} - -// Method Description: -// - Attempts to update the settings of this pane or any children of this pane. -// * If this pane is a leaf, and our profile guid matches the parameter, then -// we'll apply the new settings to our control. -// * If we're not a leaf, we'll recurse on our children. -// Arguments: -// - settings: The new TerminalSettings to apply to any matching controls -// - profile: The GUID of the profile these settings should apply to. -// Return Value: -// - -void Pane::UpdateSettings(const TerminalSettingsCreateResult& settings, const GUID& profile) -{ - if (!_IsLeaf()) - { - _firstChild->UpdateSettings(settings, profile); - _secondChild->UpdateSettings(settings, profile); - } - else - { - if (profile == _profile) - { - auto controlSettings = _control.Settings().as(); - // Update the parent of the control's settings object (and not the object itself) so - // that any overrides made by the control don't get affected by the reload - controlSettings.SetParent(settings.DefaultSettings()); - auto unfocusedSettings{ settings.UnfocusedSettings() }; - if (unfocusedSettings) - { - // Note: the unfocused settings needs to be entirely unchanged _except_ we need to - // set its parent to the settings object that lives in the control. This is because - // the overrides made by the control live in that settings object, so we want to make - // sure the unfocused settings inherit from that. - unfocusedSettings.SetParent(controlSettings); - } - _control.UnfocusedAppearance(unfocusedSettings); - _control.UpdateSettings(); - } - } -} - -// Method Description: -// - Closes one of our children. In doing so, takes the control from the other -// child, and makes this pane a leaf node again. -// Arguments: -// - closeFirst: if true, the first child should be closed, and the second -// should be preserved, and vice-versa for false. -// Return Value: -// - -void Pane::_CloseChild(const bool closeFirst) -{ - // Lock the create/close lock so that another operation won't concurrently - // modify our tree - std::unique_lock lock{ _createCloseLock }; - - // If we're a leaf, then chances are both our children closed in close - // succession. We waited on the lock while the other child was closed, so - // now we don't have a child to close anymore. Return here. When we moved - // the non-closed child into us, we also set up event handlers that will be - // triggered when we return from this. - if (_IsLeaf()) - { - return; - } - - auto closedChild = closeFirst ? _firstChild : _secondChild; - auto remainingChild = closeFirst ? _secondChild : _firstChild; - - // If the only child left is a leaf, that means we're a leaf now. - if (remainingChild->_IsLeaf()) - { - // When the remaining child is a leaf, that means both our children were - // previously leaves, and the only difference in their borders is the - // border that we gave them. Take a bitwise AND of those two children to - // remove that border. Other borders the children might have, they - // inherited from us, so the flag will be set for both children. - _borders = _firstChild->_borders & _secondChild->_borders; - - // take the control, profile and id of the pane that _wasn't_ closed. - _control = remainingChild->_control; - _connectionState = remainingChild->_connectionState; - _profile = remainingChild->_profile; - _id = remainingChild->Id(); - - // Add our new event handler before revoking the old one. - _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler }); - _warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler }); - - // Revoke the old event handlers. Remove both the handlers for the panes - // themselves closing, and remove their handlers for their controls - // closing. At this point, if the remaining child's control is closed, - // they'll trigger only our event handler for the control's close. - _firstChild->Closed(_firstClosedToken); - _secondChild->Closed(_secondClosedToken); - closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken); - remainingChild->_control.ConnectionStateChanged(remainingChild->_connectionStateChangedToken); - closedChild->_control.WarningBell(closedChild->_warningBellToken); - remainingChild->_control.WarningBell(remainingChild->_warningBellToken); - - // If either of our children was focused, we want to take that focus from - // them. - _lastActive = _firstChild->_lastActive || _secondChild->_lastActive; - - // Remove all the ui elements of our children. This'll make sure we can - // re-attach the TermControl to our Grid. - _firstChild->_root.Children().Clear(); - _secondChild->_root.Children().Clear(); - _firstChild->_border.Child(nullptr); - _secondChild->_border.Child(nullptr); - - // Reset our UI: - _root.Children().Clear(); - _border.Child(nullptr); - _root.ColumnDefinitions().Clear(); - _root.RowDefinitions().Clear(); - - // Reattach the TermControl to our grid. - _root.Children().Append(_border); - _border.Child(_control); - - // Make sure to set our _splitState before focusing the control. If you - // fail to do this, when the tab handles the GotFocus event and asks us - // what our active control is, we won't technically be a "leaf", and - // GetTerminalControl will return null. - _splitState = SplitState::None; - - // re-attach our handler for the control's GotFocus event. - _gotFocusRevoker = _control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler }); - _lostFocusRevoker = _control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler }); - - // If we're inheriting the "last active" state from one of our children, - // focus our control now. This should trigger our own GotFocus event. - if (_lastActive) - { - _control.Focus(FocusState::Programmatic); - - // See GH#7252 - // Manually fire off the GotFocus event. Typically, this is done - // automatically when the control gets focused. However, if we're - // `exit`ing a zoomed pane, then the other sibling isn't in the UI - // tree currently. So the above call to Focus won't actually focus - // the control. Because Tab is relying on GotFocus to know who the - // active pane in the tree is, without this call, _no one_ will be - // the active pane any longer. - _GotFocusHandlers(shared_from_this()); - } - - _UpdateBorders(); - - // Release our children. - _firstChild = nullptr; - _secondChild = nullptr; - } - else - { - // Determine which border flag we gave to the child when we first split - // it, so that we can take just that flag away from them. - Borders clearBorderFlag = Borders::None; - if (_splitState == SplitState::Horizontal) - { - clearBorderFlag = closeFirst ? Borders::Top : Borders::Bottom; - } - else if (_splitState == SplitState::Vertical) - { - clearBorderFlag = closeFirst ? Borders::Left : Borders::Right; - } - - // First stash away references to the old panes and their tokens - const auto oldFirstToken = _firstClosedToken; - const auto oldSecondToken = _secondClosedToken; - const auto oldFirst = _firstChild; - const auto oldSecond = _secondChild; - - // Steal all the state from our child - _splitState = remainingChild->_splitState; - _firstChild = remainingChild->_firstChild; - _secondChild = remainingChild->_secondChild; - - // Set up new close handlers on the children - _SetupChildCloseHandlers(); - - // Revoke the old event handlers on our new children - _firstChild->Closed(remainingChild->_firstClosedToken); - _secondChild->Closed(remainingChild->_secondClosedToken); - - // Revoke event handlers on old panes and controls - oldFirst->Closed(oldFirstToken); - oldSecond->Closed(oldSecondToken); - closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken); - closedChild->_control.WarningBell(closedChild->_warningBellToken); - - // Reset our UI: - _root.Children().Clear(); - _border.Child(nullptr); - _root.ColumnDefinitions().Clear(); - _root.RowDefinitions().Clear(); - - // Copy the old UI over to our grid. - // Start by copying the row/column definitions. Iterate over the - // rows/cols, and remove each one from the old grid, and attach it to - // our grid instead. - while (remainingChild->_root.ColumnDefinitions().Size() > 0) - { - auto col = remainingChild->_root.ColumnDefinitions().GetAt(0); - remainingChild->_root.ColumnDefinitions().RemoveAt(0); - _root.ColumnDefinitions().Append(col); - } - while (remainingChild->_root.RowDefinitions().Size() > 0) - { - auto row = remainingChild->_root.RowDefinitions().GetAt(0); - remainingChild->_root.RowDefinitions().RemoveAt(0); - _root.RowDefinitions().Append(row); - } - - // Remove the child's UI elements from the child's grid, so we can - // attach them to us instead. - remainingChild->_root.Children().Clear(); - remainingChild->_border.Child(nullptr); - - _root.Children().Append(_firstChild->GetRootElement()); - _root.Children().Append(_secondChild->GetRootElement()); - - // Take the flag away from the children that they inherited from their - // parent, and update their borders to visually match - WI_ClearAllFlags(_firstChild->_borders, clearBorderFlag); - WI_ClearAllFlags(_secondChild->_borders, clearBorderFlag); - _UpdateBorders(); - _firstChild->_UpdateBorders(); - _secondChild->_UpdateBorders(); - - // If the closed child was focused, transfer the focus to it's first sibling. - if (closedChild->_lastActive) - { - _FocusFirstChild(); - } - - // Release the pointers that the child was holding. - remainingChild->_firstChild = nullptr; - remainingChild->_secondChild = nullptr; - } -} - -winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst) -{ - auto weakThis{ shared_from_this() }; - - co_await winrt::resume_foreground(_root.Dispatcher()); - - if (auto pane{ weakThis.get() }) - { - // This will query if animations are enabled via the "Show animations in - // Windows" setting in the OS - winrt::Windows::UI::ViewManagement::UISettings uiSettings; - const auto animationsEnabledInOS = uiSettings.AnimationsEnabled(); - const auto animationsEnabledInApp = Media::Animation::Timeline::AllowDependentAnimations(); - - // GH#7252: If either child is zoomed, just skip the animation. It won't work. - const bool eitherChildZoomed = pane->_firstChild->_zoomed || pane->_secondChild->_zoomed; - // If animations are disabled, just skip this and go straight to - // _CloseChild. Curiously, the pane opening animation doesn't need this, - // and will skip straight to Completed when animations are disabled, but - // this one doesn't seem to. - if (!animationsEnabledInOS || !animationsEnabledInApp || eitherChildZoomed) - { - pane->_CloseChild(closeFirst); - co_return; - } - - // Setup the animation - - auto removedChild = closeFirst ? _firstChild : _secondChild; - auto remainingChild = closeFirst ? _secondChild : _firstChild; - const bool splitWidth = _splitState == SplitState::Vertical; - const auto totalSize = splitWidth ? _root.ActualWidth() : _root.ActualHeight(); - - Size removedOriginalSize{ - ::base::saturated_cast(removedChild->_root.ActualWidth()), - ::base::saturated_cast(removedChild->_root.ActualHeight()) - }; - Size remainingOriginalSize{ - ::base::saturated_cast(remainingChild->_root.ActualWidth()), - ::base::saturated_cast(remainingChild->_root.ActualHeight()) - }; - - // Remove both children from the grid - _root.Children().Clear(); - // Add the remaining child back to the grid, in the right place. - _root.Children().Append(remainingChild->GetRootElement()); - if (_splitState == SplitState::Vertical) - { - Controls::Grid::SetColumn(remainingChild->GetRootElement(), closeFirst ? 1 : 0); - } - else if (_splitState == SplitState::Horizontal) - { - Controls::Grid::SetRow(remainingChild->GetRootElement(), closeFirst ? 1 : 0); - } - - // Create the dummy grid. This grid will be the one we actually animate, - // in the place of the closed pane. - Controls::Grid dummyGrid; - dummyGrid.Background(s_unfocusedBorderBrush); - // It should be the size of the closed pane. - dummyGrid.Width(removedOriginalSize.Width); - dummyGrid.Height(removedOriginalSize.Height); - // Put it where the removed child is - if (_splitState == SplitState::Vertical) - { - Controls::Grid::SetColumn(dummyGrid, closeFirst ? 0 : 1); - } - else if (_splitState == SplitState::Horizontal) - { - Controls::Grid::SetRow(dummyGrid, closeFirst ? 0 : 1); - } - // Add it to the tree - _root.Children().Append(dummyGrid); - - // Set up the rows/cols as auto/auto, so they'll only use the size of - // the elements in the grid. - // - // * For the closed pane, we want to make that row/col "auto" sized, so - // it takes up as much space as is available. - // * For the remaining pane, we'll make that row/col "*" sized, so it - // takes all the remaining space. As the dummy grid is resized down, - // the remaining pane will expand to take the rest of the space. - _root.ColumnDefinitions().Clear(); - _root.RowDefinitions().Clear(); - if (_splitState == SplitState::Vertical) - { - auto firstColDef = Controls::ColumnDefinition(); - auto secondColDef = Controls::ColumnDefinition(); - firstColDef.Width(!closeFirst ? GridLengthHelper::FromValueAndType(1, GridUnitType::Star) : GridLengthHelper::Auto()); - secondColDef.Width(closeFirst ? GridLengthHelper::FromValueAndType(1, GridUnitType::Star) : GridLengthHelper::Auto()); - _root.ColumnDefinitions().Append(firstColDef); - _root.ColumnDefinitions().Append(secondColDef); - } - else if (_splitState == SplitState::Horizontal) - { - auto firstRowDef = Controls::RowDefinition(); - auto secondRowDef = Controls::RowDefinition(); - firstRowDef.Height(!closeFirst ? GridLengthHelper::FromValueAndType(1, GridUnitType::Star) : GridLengthHelper::Auto()); - secondRowDef.Height(closeFirst ? GridLengthHelper::FromValueAndType(1, GridUnitType::Star) : GridLengthHelper::Auto()); - _root.RowDefinitions().Append(firstRowDef); - _root.RowDefinitions().Append(secondRowDef); - } - - // Animate the dummy grid from its current size down to 0 - Media::Animation::DoubleAnimation animation{}; - animation.Duration(AnimationDuration); - animation.From(splitWidth ? removedOriginalSize.Width : removedOriginalSize.Height); - animation.To(0.0); - // This easing is the same as the entrance animation. - animation.EasingFunction(Media::Animation::QuadraticEase{}); - animation.EnableDependentAnimation(true); - - Media::Animation::Storyboard s; - s.Duration(AnimationDuration); - s.Children().Append(animation); - s.SetTarget(animation, dummyGrid); - s.SetTargetProperty(animation, splitWidth ? L"Width" : L"Height"); - - // Start the animation. - s.Begin(); - - std::weak_ptr weakThis{ shared_from_this() }; - - // When the animation is completed, reparent the child's content up to - // us, and remove the child nodes from the tree. - animation.Completed([weakThis, closeFirst](auto&&, auto&&) { - if (auto pane{ weakThis.lock() }) - { - // We don't need to manually undo any of the above trickiness. - // We're going to re-parent the child's content into us anyways - pane->_CloseChild(closeFirst); - } - }); - } -} - -// Method Description: -// - Adds event handlers to our children to handle their close events. -// Arguments: -// - -// Return Value: -// - -void Pane::_SetupChildCloseHandlers() -{ - _firstClosedToken = _firstChild->Closed([this](auto&& /*s*/, auto&& /*e*/) { - _CloseChildRoutine(true); - }); - - _secondClosedToken = _secondChild->Closed([this](auto&& /*s*/, auto&& /*e*/) { - _CloseChildRoutine(false); - }); -} - -// Method Description: -// - Sets up row/column definitions for this pane. There are three total -// row/cols. The middle one is for the separator. The first and third are for -// each of the child panes, and are given a size in pixels, based off the -// available space, and the percent of the space they respectively consume, -// which is stored in _desiredSplitPosition -// - Does nothing if our split state is currently set to SplitState::None -// Arguments: -// - -// Return Value: -// - -void Pane::_CreateRowColDefinitions() -{ - const auto first = _desiredSplitPosition * 100.0f; - const auto second = 100.0f - first; - if (_splitState == SplitState::Vertical) - { - _root.ColumnDefinitions().Clear(); - - // Create two columns in this grid: one for each pane - - auto firstColDef = Controls::ColumnDefinition(); - firstColDef.Width(GridLengthHelper::FromValueAndType(first, GridUnitType::Star)); - - auto secondColDef = Controls::ColumnDefinition(); - secondColDef.Width(GridLengthHelper::FromValueAndType(second, GridUnitType::Star)); - - _root.ColumnDefinitions().Append(firstColDef); - _root.ColumnDefinitions().Append(secondColDef); - } - else if (_splitState == SplitState::Horizontal) - { - _root.RowDefinitions().Clear(); - - // Create two rows in this grid: one for each pane - - auto firstRowDef = Controls::RowDefinition(); - firstRowDef.Height(GridLengthHelper::FromValueAndType(first, GridUnitType::Star)); - - auto secondRowDef = Controls::RowDefinition(); - secondRowDef.Height(GridLengthHelper::FromValueAndType(second, GridUnitType::Star)); - - _root.RowDefinitions().Append(firstRowDef); - _root.RowDefinitions().Append(secondRowDef); - } -} - -// Method Description: -// - Sets the thickness of each side of our borders to match our _borders state. -// Arguments: -// - -// Return Value: -// - -void Pane::_UpdateBorders() -{ - double top = 0, bottom = 0, left = 0, right = 0; - - Thickness newBorders{ 0 }; - if (_zoomed) - { - // When the pane is zoomed, manually show all the borders around the window. - top = bottom = right = left = PaneBorderSize; - } - else - { - if (WI_IsFlagSet(_borders, Borders::Top)) - { - top = PaneBorderSize; - } - if (WI_IsFlagSet(_borders, Borders::Bottom)) - { - bottom = PaneBorderSize; - } - if (WI_IsFlagSet(_borders, Borders::Left)) - { - left = PaneBorderSize; - } - if (WI_IsFlagSet(_borders, Borders::Right)) - { - right = PaneBorderSize; - } - } - _border.BorderThickness(ThicknessHelper::FromLengths(left, top, right, bottom)); -} - -// Method Description: -// - Sets the row/column of our child UI elements, to match our current split type. -// Arguments: -// - -// Return Value: -// - -void Pane::_ApplySplitDefinitions() -{ - if (_splitState == SplitState::Vertical) - { - Controls::Grid::SetColumn(_firstChild->GetRootElement(), 0); - Controls::Grid::SetColumn(_secondChild->GetRootElement(), 1); - - _firstChild->_borders = _borders | Borders::Right; - _secondChild->_borders = _borders | Borders::Left; - _borders = Borders::None; - - _UpdateBorders(); - _firstChild->_UpdateBorders(); - _secondChild->_UpdateBorders(); - } - else if (_splitState == SplitState::Horizontal) - { - Controls::Grid::SetRow(_firstChild->GetRootElement(), 0); - Controls::Grid::SetRow(_secondChild->GetRootElement(), 1); - - _firstChild->_borders = _borders | Borders::Bottom; - _secondChild->_borders = _borders | Borders::Top; - _borders = Borders::None; - - _UpdateBorders(); - _firstChild->_UpdateBorders(); - _secondChild->_UpdateBorders(); - } -} - -// Method Description: -// - Create a pair of animations when a new control enters this pane. This -// should _ONLY_ be called in _Split, AFTER the first and second child panes -// have been set up. -void Pane::_SetupEntranceAnimation() -{ - // This will query if animations are enabled via the "Show animations in - // Windows" setting in the OS - winrt::Windows::UI::ViewManagement::UISettings uiSettings; - const auto animationsEnabledInOS = uiSettings.AnimationsEnabled(); - const auto animationsEnabledInApp = Media::Animation::Timeline::AllowDependentAnimations(); - - const bool splitWidth = _splitState == SplitState::Vertical; - const auto totalSize = splitWidth ? _root.ActualWidth() : _root.ActualHeight(); - // If we don't have a size yet, it's likely that we're in startup, or we're - // being executed as a sequence of actions. In that case, just skip the - // animation. - if (totalSize <= 0 || !animationsEnabledInOS || !animationsEnabledInApp) - { - return; - } - - const auto [firstSize, secondSize] = _CalcChildrenSizes(::base::saturated_cast(totalSize)); - - // This is safe to capture this, because it's only being called in the - // context of this method (not on another thread) - auto setupAnimation = [&](const auto& size, const bool isFirstChild) { - auto child = isFirstChild ? _firstChild : _secondChild; - auto childGrid = child->_root; - auto control = child->_control; - // Build up our animation: - // * it'll take as long as our duration (200ms) - // * it'll change the value of our property from 0 to secondSize - // * it'll animate that value using a quadratic function (like f(t) = t^2) - // * IMPORTANT! We'll manually tell the animation that "yes we know what - // we're doing, we want an animation here." - Media::Animation::DoubleAnimation animation{}; - animation.Duration(AnimationDuration); - if (isFirstChild) - { - // If we're animating the first pane, the size should decrease, from - // the full size down to the given size. - animation.From(totalSize); - animation.To(size); - } - else - { - // Otherwise, we want to show the pane getting larger, so animate - // from 0 to the requested size. - animation.From(0.0); - animation.To(size); - } - animation.EasingFunction(Media::Animation::QuadraticEase{}); - animation.EnableDependentAnimation(true); - - // Now we're going to set up the Storyboard. This is a unit that uses the - // Animation from above, and actually applies it to a property. - // * we'll set it up for the same duration as the animation we have - // * Apply the animation to the grid of the new pane we're adding to the tree. - // * apply the animation to the Width or Height property. - Media::Animation::Storyboard s; - s.Duration(AnimationDuration); - s.Children().Append(animation); - s.SetTarget(animation, childGrid); - s.SetTargetProperty(animation, splitWidth ? L"Width" : L"Height"); - - // BE TRICKY: - // We're animating the width or height of our child pane's grid. - // - // We DON'T want to change the size of the control itself, because the - // terminal has to reflow the buffer every time the control changes size. So - // what we're going to do there is manually set the control's size to how - // big we _actually know_ the control will be. - // - // We're also going to be changing alignment of our child pane and the - // control. This way, we'll be able to have the control stick to the inside - // of the child pane's grid (the side that's moving), while we also have the - // pane's grid stick to "outside" of the grid (the side that's not moving) - if (splitWidth) - { - // If we're animating the first child, then stick to the top/left of - // the parent pane, otherwise use the bottom/right. This is always - // the "outside" of the parent pane. - childGrid.HorizontalAlignment(isFirstChild ? HorizontalAlignment::Left : HorizontalAlignment::Right); - control.HorizontalAlignment(HorizontalAlignment::Left); - control.Width(isFirstChild ? totalSize : size); - - // When the animation is completed, undo the trickiness from before, to - // restore the controls to the behavior they'd usually have. - animation.Completed([childGrid, control](auto&&, auto&&) { - control.Width(NAN); - childGrid.Width(NAN); - childGrid.HorizontalAlignment(HorizontalAlignment::Stretch); - control.HorizontalAlignment(HorizontalAlignment::Stretch); - }); - } - else - { - // If we're animating the first child, then stick to the top/left of - // the parent pane, otherwise use the bottom/right. This is always - // the "outside" of the parent pane. - childGrid.VerticalAlignment(isFirstChild ? VerticalAlignment::Top : VerticalAlignment::Bottom); - control.VerticalAlignment(VerticalAlignment::Top); - control.Height(isFirstChild ? totalSize : size); - - // When the animation is completed, undo the trickiness from before, to - // restore the controls to the behavior they'd usually have. - animation.Completed([childGrid, control](auto&&, auto&&) { - control.Height(NAN); - childGrid.Height(NAN); - childGrid.VerticalAlignment(VerticalAlignment::Stretch); - control.VerticalAlignment(VerticalAlignment::Stretch); - }); - } - - // Start the animation. - s.Begin(); - }; - - // TODO: GH#7365 - animating the first child right now doesn't _really_ do - // anything. We could do better though. - setupAnimation(firstSize, true); - setupAnimation(secondSize, false); -} - -// Method Description: -// - This is a helper to determine if a given Pane can be split, but without -// using the ActualWidth() and ActualHeight() methods. This is used during -// processing of many "split-pane" commands, which could happen _before_ we've -// laid out a Pane for the first time. When this happens, the Pane's don't -// have an actual size yet. However, we'd still like to figure out if the pane -// could be split, once they're all laid out. -// - This method assumes that the Pane we're attempting to split is `target`, -// and this method should be called on the root of a tree of Panes. -// - We'll walk down the tree attempting to find `target`. As we traverse the -// tree, we'll reduce the size passed to each subsequent recursive call. The -// size passed to this method represents how much space this Pane _will_ have -// to use. -// * If this pane is a leaf, and it's the pane we're looking for, use the -// available space to calculate which direction to split in. -// * If this pane is _any other leaf_, then just return nullopt, to indicate -// that the `target` Pane is not down this branch. -// * If this pane is a parent, calculate how much space our children will be -// able to use, and recurse into them. -// Arguments: -// - target: The Pane we're attempting to split. -// - splitType: The direction we're attempting to split in. -// - availableSpace: The theoretical space that's available for this pane to be able to split. -// Return Value: -// - nullopt if `target` is not this pane or a child of this pane, otherwise -// true iff we could split this pane, given `availableSpace` -// Note: -// - This method is highly similar to Pane::PreCalculateAutoSplit -std::optional Pane::PreCalculateCanSplit(const std::shared_ptr target, - SplitState splitType, - const float splitSize, - const winrt::Windows::Foundation::Size availableSpace) const -{ - if (_IsLeaf()) - { - if (target.get() == this) - { - const auto firstPrecent = 1.0f - splitSize; - const auto secondPercent = splitSize; - // If this pane is a leaf, and it's the pane we're looking for, use - // the available space to calculate which direction to split in. - const Size minSize = _GetMinSize(); - - if (splitType == SplitState::None) - { - return { false }; - } - - else if (splitType == SplitState::Vertical) - { - const auto widthMinusSeparator = availableSpace.Width - CombinedPaneBorderSize; - const auto newFirstWidth = widthMinusSeparator * firstPrecent; - const auto newSecondWidth = widthMinusSeparator * secondPercent; - - return { newFirstWidth > minSize.Width && newSecondWidth > minSize.Width }; - } - - else if (splitType == SplitState::Horizontal) - { - const auto heightMinusSeparator = availableSpace.Height - CombinedPaneBorderSize; - const auto newFirstHeight = heightMinusSeparator * firstPrecent; - const auto newSecondHeight = heightMinusSeparator * secondPercent; - - return { newFirstHeight > minSize.Height && newSecondHeight > minSize.Height }; - } - } - else - { - // If this pane is _any other leaf_, then just return nullopt, to - // indicate that the `target` Pane is not down this branch. - return std::nullopt; - } - } - else - { - // If this pane is a parent, calculate how much space our children will - // be able to use, and recurse into them. - - const bool isVerticalSplit = _splitState == SplitState::Vertical; - const float firstWidth = isVerticalSplit ? - (availableSpace.Width * _desiredSplitPosition) - PaneBorderSize : - availableSpace.Width; - const float secondWidth = isVerticalSplit ? - (availableSpace.Width - firstWidth) - PaneBorderSize : - availableSpace.Width; - const float firstHeight = !isVerticalSplit ? - (availableSpace.Height * _desiredSplitPosition) - PaneBorderSize : - availableSpace.Height; - const float secondHeight = !isVerticalSplit ? - (availableSpace.Height - firstHeight) - PaneBorderSize : - availableSpace.Height; - - const auto firstResult = _firstChild->PreCalculateCanSplit(target, splitType, splitSize, { firstWidth, firstHeight }); - return firstResult.has_value() ? firstResult : _secondChild->PreCalculateCanSplit(target, splitType, splitSize, { secondWidth, secondHeight }); - } - - // We should not possibly be getting here - both the above branches should - // return a value. - FAIL_FAST(); -} - -// Method Description: -// - Split the focused pane in our tree of panes, and place the given -// TermControl into the newly created pane. If we're the focused pane, then -// we'll create two new children, and place them side-by-side in our Grid. -// Arguments: -// - splitType: what type of split we want to create. -// - profile: The profile GUID to associate with the newly created pane. -// - control: A TermControl to use in the new pane. -// Return Value: -// - The two newly created Panes -std::pair, std::shared_ptr> Pane::Split(SplitState splitType, - const float splitSize, - const GUID& profile, - const TermControl& control) -{ - if (!_IsLeaf()) - { - if (_firstChild->_HasFocusedChild()) - { - return _firstChild->Split(splitType, splitSize, profile, control); - } - else if (_secondChild->_HasFocusedChild()) - { - return _secondChild->Split(splitType, splitSize, profile, control); - } - - return { nullptr, nullptr }; - } - - return _Split(splitType, splitSize, profile, control); -} - -// Method Description: -// - Converts an "automatic" split type into either Vertical or Horizontal, -// based upon the current dimensions of the Pane. -// - If any of the other SplitState values are passed in, they're returned -// unmodified. -// Arguments: -// - splitType: The SplitState to attempt to convert -// Return Value: -// - None if splitType was None, otherwise one of Horizontal or Vertical -SplitState Pane::_convertAutomaticSplitState(const SplitState& splitType) const -{ - // Careful here! If the pane doesn't yet have a size, these dimensions will - // be 0, and we'll always return Vertical. - - if (splitType == SplitState::Automatic) - { - // If the requested split type was "auto", determine which direction to - // split based on our current dimensions - const Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), - gsl::narrow_cast(_root.ActualHeight()) }; - return actualSize.Width >= actualSize.Height ? SplitState::Vertical : SplitState::Horizontal; - } - return splitType; -} - -// Method Description: -// - Does the bulk of the work of creating a new split. Initializes our UI, -// creates a new Pane to host the control, registers event handlers. -// Arguments: -// - splitType: what type of split we should create. -// - profile: The profile GUID to associate with the newly created pane. -// - control: A TermControl to use in the new pane. -// Return Value: -// - The two newly created Panes -std::pair, std::shared_ptr> Pane::_Split(SplitState splitType, - const float splitSize, - const GUID& profile, - const TermControl& control) -{ - if (splitType == SplitState::None) - { - return { nullptr, nullptr }; - } - - auto actualSplitType = _convertAutomaticSplitState(splitType); - - // Lock the create/close lock so that another operation won't concurrently - // modify our tree - std::unique_lock lock{ _createCloseLock }; - - // revoke our handler - the child will take care of the control now. - _control.ConnectionStateChanged(_connectionStateChangedToken); - _connectionStateChangedToken.value = 0; - _control.WarningBell(_warningBellToken); - _warningBellToken.value = 0; - - // Remove our old GotFocus handler from the control. We don't what the - // control telling us that it's now focused, we want it telling its new - // parent. - _gotFocusRevoker.revoke(); - _lostFocusRevoker.revoke(); - - _splitState = actualSplitType; - _desiredSplitPosition = 1.0f - splitSize; - - // Remove any children we currently have. We can't add the existing - // TermControl to a new grid until we do this. - _root.Children().Clear(); - _border.Child(nullptr); - - // Create two new Panes - // Move our control, guid into the first one. - // Move the new guid, control into the second. - _firstChild = std::make_shared(_profile.value(), _control); - _firstChild->_connectionState = std::exchange(_connectionState, ConnectionState::NotConnected); - _profile = std::nullopt; - _control = { nullptr }; - _secondChild = std::make_shared(profile, control); - - _CreateRowColDefinitions(); - - _root.Children().Append(_firstChild->GetRootElement()); - _root.Children().Append(_secondChild->GetRootElement()); - - _ApplySplitDefinitions(); - - // Register event handlers on our children to handle their Close events - _SetupChildCloseHandlers(); - - _lastActive = false; - - _SetupEntranceAnimation(); - - // Clear out our ID, only leaves should have IDs - _id = {}; - - return { _firstChild, _secondChild }; -} - -// Method Description: -// - Recursively attempt to "zoom" the given pane. When the pane is zoomed, it -// won't be displayed as part of the tab tree, instead it'll take up the full -// content of the tab. When we find the given pane, we'll need to remove it -// from the UI tree, so that the caller can re-add it. We'll also set some -// internal state, so the pane can display all of its borders. -// Arguments: -// - zoomedPane: This is the pane which we're attempting to zoom on. -// Return Value: -// - -void Pane::Maximize(std::shared_ptr zoomedPane) -{ - if (_IsLeaf()) - { - _zoomed = (zoomedPane == shared_from_this()); - _UpdateBorders(); - } - else - { - if (zoomedPane == _firstChild || zoomedPane == _secondChild) - { - // When we're zooming the pane, we'll need to remove it from our UI - // tree. Easy way: just remove both children. We'll re-attach both - // when we un-zoom. - _root.Children().Clear(); - } - - // Always recurse into both children. If the (un)zoomed pane was one of - // our direct children, we'll still want to update it's borders. - _firstChild->Maximize(zoomedPane); - _secondChild->Maximize(zoomedPane); - } -} - -// Method Description: -// - Recursively attempt to "un-zoom" the given pane. This does the opposite of -// Pane::Maximize. When we find the given pane, we should return the pane to our -// UI tree. We'll also clear the internal state, so the pane can display its -// borders correctly. -// - The caller should make sure to have removed the zoomed pane from the UI -// tree _before_ calling this. -// Arguments: -// - zoomedPane: This is the pane which we're attempting to un-zoom. -// Return Value: -// - -void Pane::Restore(std::shared_ptr zoomedPane) -{ - if (_IsLeaf()) - { - _zoomed = false; - _UpdateBorders(); - } - else - { - if (zoomedPane == _firstChild || zoomedPane == _secondChild) - { - // When we're un-zooming the pane, we'll need to re-add it to our UI - // tree where it originally belonged. easy way: just re-add both. - _root.Children().Clear(); - _root.Children().Append(_firstChild->GetRootElement()); - _root.Children().Append(_secondChild->GetRootElement()); - } - - // Always recurse into both children. If the (un)zoomed pane was one of - // our direct children, we'll still want to update it's borders. - _firstChild->Restore(zoomedPane); - _secondChild->Restore(zoomedPane); - } -} - -// Method Description: -// - Retrieves the ID of this pane -// - NOTE: The caller should make sure that this pane is a leaf, -// otherwise the ID value will not make sense (leaves have IDs, parents do not) -// Return Value: -// - The ID of this pane -std::optional Pane::Id() noexcept -{ - return _id; -} - -// Method Description: -// - Sets this pane's ID -// - Panes are given IDs upon creation by TerminalTab -// Arguments: -// - The number to set this pane's ID to -void Pane::Id(uint32_t id) noexcept -{ - _id = id; -} - -// Method Description: -// - Recursive function that focuses a pane with the given ID -// Arguments: -// - The ID of the pane we want to focus -bool Pane::FocusPane(const uint32_t id) -{ - if (_IsLeaf() && id == _id) - { - // Make sure to use _FocusFirstChild here - that'll properly update the - // focus if we're in startup. - _FocusFirstChild(); - return true; - } - else - { - if (_firstChild && _secondChild) - { - return _firstChild->FocusPane(id) || - _secondChild->FocusPane(id); - } - } - return false; -} - -// Method Description: -// - Gets the size in pixels of each of our children, given the full size they -// should fill. Since these children own their own separators (borders), this -// size is their portion of our _entire_ size. If specified size is lower than -// required then children will be of minimum size. Snaps first child to grid -// but not the second. -// Arguments: -// - fullSize: the amount of space in pixels that should be filled by our -// children and their separators. Can be arbitrarily low. -// Return Value: -// - a pair with the size of our first child and the size of our second child, -// respectively. -std::pair Pane::_CalcChildrenSizes(const float fullSize) const -{ - const auto widthOrHeight = _splitState == SplitState::Vertical; - const auto snappedSizes = _CalcSnappedChildrenSizes(widthOrHeight, fullSize).lower; - - // Keep the first pane snapped and give the second pane all remaining size - return { - snappedSizes.first, - fullSize - snappedSizes.first - }; -} - -// Method Description: -// - Gets the size in pixels of each of our children, given the full size they should -// fill. Each child is snapped to char grid as close as possible. If called multiple -// times with fullSize argument growing, then both returned sizes are guaranteed to be -// non-decreasing (it's a monotonically increasing function). This is important so that -// user doesn't get any pane shrank when they actually expand the window or parent pane. -// That is also required by the layout algorithm. -// Arguments: -// - widthOrHeight: if true, operates on width, otherwise on height. -// - fullSize: the amount of space in pixels that should be filled by our children and -// their separator. Can be arbitrarily low. -// Return Value: -// - a structure holding the result of this calculation. The 'lower' field represents the -// children sizes that would fit in the fullSize, but might (and usually do) not fill it -// completely. The 'higher' field represents the size of the children if they slightly exceed -// the fullSize, but are snapped. If the children can be snapped and also exactly match -// the fullSize, then both this fields have the same value that represent this situation. -Pane::SnapChildrenSizeResult Pane::_CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const -{ - if (_IsLeaf()) - { - THROW_HR(E_FAIL); - } - - // First we build a tree of nodes corresponding to the tree of our descendant panes. - // Each node represents a size of given pane. At the beginning, each node has the minimum - // size that the corresponding pane can have; so has the our (root) node. We then gradually - // expand our node (which in turn expands some of the child nodes) until we hit the desired - // size. Since each expand step (done in _AdvanceSnappedDimension()) guarantees that all the - // sizes will be snapped, our return values is also snapped. - // Why do we do it this, iterative way? Why can't we just split the given size by - // _desiredSplitPosition and snap it latter? Because it's hardly doable, if possible, to also - // fulfill the monotonicity requirement that way. As the fullSize increases, the proportional - // point that separates children panes also moves and cells sneak in the available area in - // unpredictable way, regardless which child has the snap priority or whether we snap them - // upward, downward or to nearest. - // With present way we run the same sequence of actions regardless to the fullSize value and - // only just stop at various moments when the built sizes reaches it. Eventually, this could - // be optimized for simple cases like when both children are both leaves with the same character - // size, but it doesn't seem to be beneficial. - - auto sizeTree = _CreateMinSizeTree(widthOrHeight); - LayoutSizeNode lastSizeTree{ sizeTree }; - - while (sizeTree.size < fullSize) - { - lastSizeTree = sizeTree; - _AdvanceSnappedDimension(widthOrHeight, sizeTree); - - if (sizeTree.size == fullSize) - { - // If we just hit exactly the requested value, then just return the - // current state of children. - return { { sizeTree.firstChild->size, sizeTree.secondChild->size }, - { sizeTree.firstChild->size, sizeTree.secondChild->size } }; - } - } - - // We exceeded the requested size in the loop above, so lastSizeTree will have - // the last good sizes (so that children fit in) and sizeTree has the next possible - // snapped sizes. Return them as lower and higher snap possibilities. - return { { lastSizeTree.firstChild->size, lastSizeTree.secondChild->size }, - { sizeTree.firstChild->size, sizeTree.secondChild->size } }; -} - -// Method Description: -// - Adjusts given dimension (width or height) so that all descendant terminals -// align with their character grids as close as possible. Snaps to closes match -// (either upward or downward). Also makes sure to fit in minimal sizes of the panes. -// Arguments: -// - widthOrHeight: if true operates on width, otherwise on height -// - dimension: a dimension (width or height) to snap -// Return Value: -// - A value corresponding to the next closest snap size for this Pane, either upward or downward -float Pane::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const -{ - const auto [lower, higher] = _CalcSnappedDimension(widthOrHeight, dimension); - return dimension - lower < higher - dimension ? lower : higher; -} - -// Method Description: -// - Adjusts given dimension (width or height) so that all descendant terminals -// align with their character grids as close as possible. Also makes sure to -// fit in minimal sizes of the panes. -// Arguments: -// - widthOrHeight: if true operates on width, otherwise on height -// - dimension: a dimension (width or height) to be snapped -// Return Value: -// - pair of floats, where first value is the size snapped downward (not greater then -// requested size) and second is the size snapped upward (not lower than requested size). -// If requested size is already snapped, then both returned values equal this value. -Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const -{ - if (_IsLeaf()) - { - // If we're a leaf pane, align to the grid of controlling terminal - - const auto minSize = _GetMinSize(); - const auto minDimension = widthOrHeight ? minSize.Width : minSize.Height; - - if (dimension <= minDimension) - { - return { minDimension, minDimension }; - } - - float lower = _control.SnapDimensionToGrid(widthOrHeight, dimension); - if (widthOrHeight) - { - lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; - lower += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; - } - else - { - lower += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; - lower += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; - } - - if (lower == dimension) - { - // If we happen to be already snapped, then just return this size - // as both lower and higher values. - return { lower, lower }; - } - else - { - const auto cellSize = _control.CharacterDimensions(); - const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height); - return { lower, higher }; - } - } - else if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) - { - // If we're resizing along separator axis, snap to the closest possibility - // given by our children panes. - - const auto firstSnapped = _firstChild->_CalcSnappedDimension(widthOrHeight, dimension); - const auto secondSnapped = _secondChild->_CalcSnappedDimension(widthOrHeight, dimension); - return { - std::max(firstSnapped.lower, secondSnapped.lower), - std::min(firstSnapped.higher, secondSnapped.higher) - }; - } - else - { - // If we're resizing perpendicularly to separator axis, calculate the sizes - // of child panes that would fit the given size. We use same algorithm that - // is used for real resize routine, but exclude the remaining empty space that - // would appear after the second pane. This will be the 'downward' snap possibility, - // while the 'upward' will be given as a side product of the layout function. - - const auto childSizes = _CalcSnappedChildrenSizes(widthOrHeight, dimension); - return { - childSizes.lower.first + childSizes.lower.second, - childSizes.higher.first + childSizes.higher.second - }; - } -} - -// Method Description: -// - Increases size of given LayoutSizeNode to match next possible 'snap'. In case of leaf -// pane this means the next cell of the terminal. Otherwise it means that one of its children -// advances (recursively). It expects the given node and its descendants to have either -// already snapped or minimum size. -// Arguments: -// - widthOrHeight: if true operates on width, otherwise on height. -// - sizeNode: a layout size node that corresponds to this pane. -// Return Value: -// - -void Pane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const -{ - if (_IsLeaf()) - { - // We're a leaf pane, so just add one more row or column (unless isMinimumSize - // is true, see below). - - if (sizeNode.isMinimumSize) - { - // If the node is of its minimum size, this size might not be snapped (it might - // be, say, half a character, or fixed 10 pixels), so snap it upward. It might - // however be already snapped, so add 1 to make sure it really increases - // (not strictly necessary but to avoid surprises). - sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher; - } - else - { - const auto cellSize = _control.CharacterDimensions(); - sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height; - } - } - else - { - // We're a parent pane, so we have to advance dimension of our children panes. In - // fact, we advance only one child (chosen later) to keep the growth fine-grained. - - // To choose which child pane to advance, we actually need to know their advanced sizes - // in advance (oh), to see which one would 'fit' better. Often, this is already cached - // by the previous invocation of this function in nextFirstChild and nextSecondChild - // fields of given node. If not, we need to calculate them now. - if (sizeNode.nextFirstChild == nullptr) - { - sizeNode.nextFirstChild = std::make_unique(*sizeNode.firstChild); - _firstChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); - } - if (sizeNode.nextSecondChild == nullptr) - { - sizeNode.nextSecondChild = std::make_unique(*sizeNode.secondChild); - _secondChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); - } - - const auto nextFirstSize = sizeNode.nextFirstChild->size; - const auto nextSecondSize = sizeNode.nextSecondChild->size; - - // Choose which child pane to advance. - bool advanceFirstOrSecond; - if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) - { - // If we're growing along separator axis, choose the child that - // wants to be smaller than the other, so that the resulting size - // will be the smallest. - advanceFirstOrSecond = nextFirstSize < nextSecondSize; - } - else - { - // If we're growing perpendicularly to separator axis, choose a - // child so that their size ratio is closer to that we're trying - // to maintain (this is, the relative separator position is closer - // to the _desiredSplitPosition field). - - const auto firstSize = sizeNode.firstChild->size; - const auto secondSize = sizeNode.secondChild->size; - - // Because we rely on equality check, these calculations have to be - // immune to floating point errors. In common situation where both panes - // have the same character sizes and _desiredSplitPosition is 0.5 (or - // some simple fraction) both ratios will often be the same, and if so - // we always take the left child. It could be right as well, but it's - // important that it's consistent: that it would always go - // 1 -> 2 -> 1 -> 2 -> 1 -> 2 and not like 1 -> 1 -> 2 -> 2 -> 2 -> 1 - // which would look silly to the user but which occur if there was - // a non-floating-point-safe math. - const auto deviation1 = nextFirstSize - (nextFirstSize + secondSize) * _desiredSplitPosition; - const auto deviation2 = -1 * (firstSize - (firstSize + nextSecondSize) * _desiredSplitPosition); - advanceFirstOrSecond = deviation1 <= deviation2; - } - - // Here we advance one of our children. Because we already know the appropriate - // (advanced) size that given child would need to have, we simply assign that size - // to it. We then advance its 'next*' size (nextFirstChild or nextSecondChild) so - // the invariant holds (as it will likely be used by the next invocation of this - // function). The other child's next* size remains unchanged because its size - // haven't changed either. - if (advanceFirstOrSecond) - { - *sizeNode.firstChild = *sizeNode.nextFirstChild; - _firstChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); - } - else - { - *sizeNode.secondChild = *sizeNode.nextSecondChild; - _secondChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); - } - - // Since the size of one of our children has changed we need to update our size as well. - if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) - { - sizeNode.size = std::max(sizeNode.firstChild->size, sizeNode.secondChild->size); - } - else - { - sizeNode.size = sizeNode.firstChild->size + sizeNode.secondChild->size; - } - } - - // Because we have grown, we're certainly no longer of our - // minimal size (if we've ever been). - sizeNode.isMinimumSize = false; -} - -// Method Description: -// - Get the absolute minimum size that this pane can be resized to and still -// have 1x1 character visible, in each of its children. If we're a leaf, we'll -// include the space needed for borders _within_ us. -// Arguments: -// - -// Return Value: -// - The minimum size that this pane can be resized to and still have a visible -// character. -Size Pane::_GetMinSize() const -{ - if (_IsLeaf()) - { - auto controlSize = _control.MinimumSize(); - auto newWidth = controlSize.Width; - auto newHeight = controlSize.Height; - - newWidth += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; - newWidth += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; - newHeight += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; - newHeight += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; - - return { newWidth, newHeight }; - } - else - { - const auto firstSize = _firstChild->_GetMinSize(); - const auto secondSize = _secondChild->_GetMinSize(); - - const auto minWidth = _splitState == SplitState::Vertical ? - firstSize.Width + secondSize.Width : - std::max(firstSize.Width, secondSize.Width); - const auto minHeight = _splitState == SplitState::Horizontal ? - firstSize.Height + secondSize.Height : - std::max(firstSize.Height, secondSize.Height); - - return { minWidth, minHeight }; - } -} - -// Method Description: -// - Builds a tree of LayoutSizeNode that matches the tree of panes. Each node -// has minimum size that the corresponding pane can have. -// Arguments: -// - widthOrHeight: if true operates on width, otherwise on height -// Return Value: -// - Root node of built tree that matches this pane. -Pane::LayoutSizeNode Pane::_CreateMinSizeTree(const bool widthOrHeight) const -{ - const auto size = _GetMinSize(); - LayoutSizeNode node(widthOrHeight ? size.Width : size.Height); - if (!_IsLeaf()) - { - node.firstChild = std::make_unique(_firstChild->_CreateMinSizeTree(widthOrHeight)); - node.secondChild = std::make_unique(_secondChild->_CreateMinSizeTree(widthOrHeight)); - } - - return node; -} - -// Method Description: -// - Adjusts split position so that no child pane is smaller then its -// minimum size -// Arguments: -// - widthOrHeight: if true, operates on width, otherwise on height. -// - requestedValue: split position value to be clamped -// - totalSize: size (width or height) of the parent pane -// Return Value: -// - split position (value in range <0.0, 1.0>) -float Pane::_ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const -{ - const auto firstMinSize = _firstChild->_GetMinSize(); - const auto secondMinSize = _secondChild->_GetMinSize(); - - const auto firstMinDimension = widthOrHeight ? firstMinSize.Width : firstMinSize.Height; - const auto secondMinDimension = widthOrHeight ? secondMinSize.Width : secondMinSize.Height; - - const auto minSplitPosition = firstMinDimension / totalSize; - const auto maxSplitPosition = 1.0f - (secondMinDimension / totalSize); - - return std::clamp(requestedValue, minSplitPosition, maxSplitPosition); -} - -// Function Description: -// - Attempts to load some XAML resources that the Pane will need. This includes: -// * The Color we'll use for active Panes's borders - SystemAccentColor -// * The Brush we'll use for inactive Panes - TabViewBackground (to match the -// color of the titlebar) -// Arguments: -// - -// Return Value: -// - -void Pane::_SetupResources() -{ - const auto res = Application::Current().Resources(); - const auto accentColorKey = winrt::box_value(L"SystemAccentColor"); - if (res.HasKey(accentColorKey)) - { - const auto colorFromResources = res.Lookup(accentColorKey); - // If SystemAccentColor is _not_ a Color for some reason, use - // Transparent as the color, so we don't do this process again on - // the next pane (by leaving s_focusedBorderBrush nullptr) - auto actualColor = winrt::unbox_value_or(colorFromResources, Colors::Black()); - s_focusedBorderBrush = SolidColorBrush(actualColor); - } - else - { - // DON'T use Transparent here - if it's "Transparent", then it won't - // be able to hittest for clicks, and then clicking on the border - // will eat focus. - s_focusedBorderBrush = SolidColorBrush{ Colors::Black() }; - } - - const auto tabViewBackgroundKey = winrt::box_value(L"TabViewBackground"); - if (res.HasKey(tabViewBackgroundKey)) - { - winrt::Windows::Foundation::IInspectable obj = res.Lookup(tabViewBackgroundKey); - s_unfocusedBorderBrush = obj.try_as(); - } - else - { - // DON'T use Transparent here - if it's "Transparent", then it won't - // be able to hittest for clicks, and then clicking on the border - // will eat focus. - s_unfocusedBorderBrush = SolidColorBrush{ Colors::Black() }; - } -} - -int Pane::GetLeafPaneCount() const noexcept -{ - return _IsLeaf() ? 1 : (_firstChild->GetLeafPaneCount() + _secondChild->GetLeafPaneCount()); -} - -// Method Description: -// - This is a helper to determine which direction an "Automatic" split should -// happen in for a given pane, but without using the ActualWidth() and -// ActualHeight() methods. This is used during the initialization of the -// Terminal, when we could be processing many "split-pane" commands _before_ -// we've ever laid out the Terminal for the first time. When this happens, the -// Pane's don't have an actual size yet. However, we'd still like to figure -// out how to do an "auto" split when these Panes are all laid out. -// - This method assumes that the Pane we're attempting to split is `target`, -// and this method should be called on the root of a tree of Panes. -// - We'll walk down the tree attempting to find `target`. As we traverse the -// tree, we'll reduce the size passed to each subsequent recursive call. The -// size passed to this method represents how much space this Pane _will_ have -// to use. -// * If this pane is a leaf, and it's the pane we're looking for, use the -// available space to calculate which direction to split in. -// * If this pane is _any other leaf_, then just return nullopt, to indicate -// that the `target` Pane is not down this branch. -// * If this pane is a parent, calculate how much space our children will be -// able to use, and recurse into them. -// Arguments: -// - target: The Pane we're attempting to split. -// - availableSpace: The theoretical space that's available for this pane to be able to split. -// Return Value: -// - nullopt if `target` is not this pane or a child of this pane, otherwise the -// SplitState that `target` would use for an `Automatic` split given -// `availableSpace` -std::optional Pane::PreCalculateAutoSplit(const std::shared_ptr target, - const winrt::Windows::Foundation::Size availableSpace) const -{ - if (_IsLeaf()) - { - if (target.get() == this) - { - //If this pane is a leaf, and it's the pane we're looking for, use - //the available space to calculate which direction to split in. - return availableSpace.Width > availableSpace.Height ? SplitState::Vertical : SplitState::Horizontal; - } - else - { - // If this pane is _any other leaf_, then just return nullopt, to - // indicate that the `target` Pane is not down this branch. - return std::nullopt; - } - } - else - { - // If this pane is a parent, calculate how much space our children will - // be able to use, and recurse into them. - - const bool isVerticalSplit = _splitState == SplitState::Vertical; - const float firstWidth = isVerticalSplit ? (availableSpace.Width * _desiredSplitPosition) : availableSpace.Width; - const float secondWidth = isVerticalSplit ? (availableSpace.Width - firstWidth) : availableSpace.Width; - const float firstHeight = !isVerticalSplit ? (availableSpace.Height * _desiredSplitPosition) : availableSpace.Height; - const float secondHeight = !isVerticalSplit ? (availableSpace.Height - firstHeight) : availableSpace.Height; - - const auto firstResult = _firstChild->PreCalculateAutoSplit(target, { firstWidth, firstHeight }); - return firstResult.has_value() ? firstResult : _secondChild->PreCalculateAutoSplit(target, { secondWidth, secondHeight }); - } - - // We should not possibly be getting here - both the above branches should - // return a value. - FAIL_FAST(); -} - -// Method Description: -// - Returns true if the pane or one of its descendants is read-only -bool Pane::ContainsReadOnly() const -{ - return _IsLeaf() ? _control.ReadOnly() : (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly()); -} - -DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate>); -DEFINE_EVENT(Pane, LostFocus, _LostFocusHandlers, winrt::delegate>); -DEFINE_EVENT(Pane, PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler); diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h deleted file mode 100644 index 6e7f801b787..00000000000 --- a/src/cascadia/TerminalApp/Pane.h +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// -// Module Name: -// - Pane.h -// -// Abstract: -// - Panes are an abstraction by which the terminal can display multiple terminal -// instances simultaneously in a single terminal window. While tabs allow for -// a single terminal window to have many terminal sessions running -// simultaneously within a single window, only one tab can be visible at a -// time. Panes, on the other hand, allow a user to have many different -// terminal sessions visible to the user within the context of a single window -// at the same time. This can enable greater productivity from the user, as -// they can see the output of one terminal window while working in another. -// - See doc/cascadia/Panes.md for a detailed description. -// -// Author: -// - Mike Griese (zadjii-msft) 16-May-2019 - -#pragma once - -#include "../../cascadia/inc/cppwinrt_utils.h" - -enum class Borders : int -{ - None = 0x0, - Top = 0x1, - Bottom = 0x2, - Left = 0x4, - Right = 0x8 -}; -DEFINE_ENUM_FLAG_OPERATORS(Borders); - -class Pane : public std::enable_shared_from_this -{ -public: - Pane(const GUID& profile, - const winrt::Microsoft::Terminal::Control::TermControl& control, - const bool lastFocused = false); - - std::shared_ptr GetActivePane(); - winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl(); - std::optional GetFocusedProfile(); - - winrt::Windows::UI::Xaml::Controls::Grid GetRootElement(); - - bool WasLastFocused() const noexcept; - void UpdateVisuals(); - void ClearActive(); - void SetActive(); - - void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, - const GUID& profile); - void ResizeContent(const winrt::Windows::Foundation::Size& newSize); - void Relayout(); - bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction); - bool NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction); - - std::pair, std::shared_ptr> Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType, - const float splitSize, - const GUID& profile, - const winrt::Microsoft::Terminal::Control::TermControl& control); - float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; - std::optional PreCalculateAutoSplit(const std::shared_ptr target, - const winrt::Windows::Foundation::Size parentSize) const; - std::optional PreCalculateCanSplit(const std::shared_ptr target, - winrt::Microsoft::Terminal::Settings::Model::SplitState splitType, - const float splitSize, - const winrt::Windows::Foundation::Size availableSpace) const; - void Shutdown(); - void Close(); - - int GetLeafPaneCount() const noexcept; - - void Maximize(std::shared_ptr zoomedPane); - void Restore(std::shared_ptr zoomedPane); - - std::optional Id() noexcept; - void Id(uint32_t id) noexcept; - bool FocusPane(const uint32_t id); - - bool ContainsReadOnly() const; - - WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); - DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate>); - DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate>); - DECLARE_EVENT(PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler); - -private: - struct SnapSizeResult; - struct SnapChildrenSizeResult; - struct LayoutSizeNode; - - winrt::Windows::UI::Xaml::Controls::Grid _root{}; - winrt::Windows::UI::Xaml::Controls::Border _border{}; - winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr }; - winrt::Microsoft::Terminal::TerminalConnection::ConnectionState _connectionState{ winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::NotConnected }; - static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_focusedBorderBrush; - static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_unfocusedBorderBrush; - - std::shared_ptr _firstChild{ nullptr }; - std::shared_ptr _secondChild{ nullptr }; - winrt::Microsoft::Terminal::Settings::Model::SplitState _splitState{ winrt::Microsoft::Terminal::Settings::Model::SplitState::None }; - float _desiredSplitPosition; - - std::optional _id; - - bool _lastActive{ false }; - std::optional _profile{ std::nullopt }; - winrt::event_token _connectionStateChangedToken{ 0 }; - winrt::event_token _firstClosedToken{ 0 }; - winrt::event_token _secondClosedToken{ 0 }; - winrt::event_token _warningBellToken{ 0 }; - - winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker; - winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker; - - std::shared_mutex _createCloseLock{}; - - Borders _borders{ Borders::None }; - - bool _zoomed{ false }; - - bool _IsLeaf() const noexcept; - bool _HasFocusedChild() const noexcept; - void _SetupChildCloseHandlers(); - - std::pair, std::shared_ptr> _Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType, - const float splitSize, - const GUID& profile, - const winrt::Microsoft::Terminal::Control::TermControl& control); - - void _CreateRowColDefinitions(); - void _ApplySplitDefinitions(); - void _SetupEntranceAnimation(); - void _UpdateBorders(); - - bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction); - bool _NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction); - - void _CloseChild(const bool closeFirst); - winrt::fire_and_forget _CloseChildRoutine(const bool closeFirst); - - void _FocusFirstChild(); - void _ControlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); - void _ControlWarningBellHandler(winrt::Windows::Foundation::IInspectable const& sender, - winrt::Windows::Foundation::IInspectable const& e); - void _ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& sender, - winrt::Windows::UI::Xaml::RoutedEventArgs const& e); - void _ControlLostFocusHandler(winrt::Windows::Foundation::IInspectable const& sender, - winrt::Windows::UI::Xaml::RoutedEventArgs const& e); - - std::pair _CalcChildrenSizes(const float fullSize) const; - SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const; - SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; - void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const; - - winrt::Windows::Foundation::Size _GetMinSize() const; - LayoutSizeNode _CreateMinSizeTree(const bool widthOrHeight) const; - float _ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const; - - winrt::Microsoft::Terminal::Settings::Model::SplitState _convertAutomaticSplitState(const winrt::Microsoft::Terminal::Settings::Model::SplitState& splitType) const; - - std::optional _preCalculateAutoSplit(const std::shared_ptr target, const winrt::Windows::Foundation::Size parentSize) const; - - // Function Description: - // - Returns true if the given direction can be used with the given split - // type. - // - This is used for pane resizing (which will need a pane separator - // that's perpendicular to the direction to be able to move the separator - // in that direction). - // - Also used for moving focus between panes, which again happens _across_ a separator. - // Arguments: - // - direction: The Direction to compare - // - splitType: The winrt::TerminalApp::SplitState to compare - // Return Value: - // - true iff the direction is perpendicular to the splitType. False for - // winrt::TerminalApp::SplitState::None. - template - static constexpr bool DirectionMatchesSplit(const T& direction, - const winrt::Microsoft::Terminal::Settings::Model::SplitState& splitType) - { - if (splitType == winrt::Microsoft::Terminal::Settings::Model::SplitState::None) - { - return false; - } - else if (splitType == winrt::Microsoft::Terminal::Settings::Model::SplitState::Horizontal) - { - return direction == T::Up || - direction == T::Down; - } - else if (splitType == winrt::Microsoft::Terminal::Settings::Model::SplitState::Vertical) - { - return direction == T::Left || - direction == T::Right; - } - return false; - } - - static void _SetupResources(); - - struct SnapSizeResult - { - float lower; - float higher; - }; - - struct SnapChildrenSizeResult - { - std::pair lower; - std::pair higher; - }; - - // Helper structure that builds a (roughly) binary tree corresponding - // to the pane tree. Used for laying out panes with snapped sizes. - struct LayoutSizeNode - { - float size; - bool isMinimumSize; - std::unique_ptr firstChild; - std::unique_ptr secondChild; - - // These two fields hold next possible snapped values of firstChild and - // secondChild. Although that could be calculated from these fields themselves, - // it would be wasteful as we have to know these values more often than for - // simple increment. Hence we cache that here. - std::unique_ptr nextFirstChild; - std::unique_ptr nextSecondChild; - - explicit LayoutSizeNode(const float minSize); - LayoutSizeNode(const LayoutSizeNode& other); - - LayoutSizeNode& operator=(const LayoutSizeNode& other); - - private: - void _AssignChildNode(std::unique_ptr& nodeField, const LayoutSizeNode* const newNode); - }; -}; diff --git a/src/cascadia/TerminalApp/ParentPane.cpp b/src/cascadia/TerminalApp/ParentPane.cpp new file mode 100644 index 00000000000..b34bcbb541b --- /dev/null +++ b/src/cascadia/TerminalApp/ParentPane.cpp @@ -0,0 +1,1450 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "ParentPane.h" + +#include "ParentPane.g.cpp" + +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Graphics::Display; +using namespace winrt::Windows::UI; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Core; +using namespace winrt::Windows::UI::Xaml::Media; +using namespace winrt::Microsoft::Terminal::Settings::Model; +using namespace winrt::Microsoft::Terminal::Control; +using namespace winrt::Microsoft::Terminal::TerminalConnection; + +namespace winrt::TerminalApp::implementation +{ + static const int PaneBorderSize = 2; + static const int CombinedPaneBorderSize = 2 * PaneBorderSize; + winrt::Windows::UI::Xaml::Media::SolidColorBrush ParentPane::s_unfocusedBorderBrush = { nullptr }; + + static const int AnimationDurationInMilliseconds = 200; + static const Duration AnimationDuration = DurationHelper::FromTimeSpan(winrt::Windows::Foundation::TimeSpan(std::chrono::milliseconds(AnimationDurationInMilliseconds))); + + ParentPane::ParentPane(TerminalApp::LeafPane firstChild, TerminalApp::LeafPane secondChild, SplitState splitState, float splitPosition) : + _firstChild(firstChild), + _secondChild(secondChild), + _splitState(splitState), + _desiredSplitPosition(splitPosition) + + { + InitializeComponent(); + + _CreateRowColDefinitions(); + + _GridLayoutHelper(); + + _firstLayoutRevoker = firstChild.TerminalControl().LayoutUpdated(winrt::auto_revoke, [&](auto /*s*/, auto /*e*/) { + _ChildrenLayoutUpdatedHelper(true); + }); + + _secondLayoutRevoker = secondChild.TerminalControl().LayoutUpdated(winrt::auto_revoke, [&](auto /*s*/, auto /*e*/) { + _ChildrenLayoutUpdatedHelper(false); + }); + + _SetupResources(); + } + + // Method Description: + // - Get the root UIElement of this pane. In our case is a grid + // with 2 content presenters, one for each child + // Return Value: + // - the Grid acting as the root of this pane. + Controls::Grid ParentPane::GetRootElement() + { + return Root(); + } + + // Method Description: + // - Updates the settings of the children of this pane recursively + // Arguments: + // - settings: The new TerminalSettings to apply to any matching controls + // - profile: The GUID of the profile these settings should apply to + void ParentPane::UpdateSettings(const TerminalSettingsCreateResult& settings, const GUID& profile) + { + _firstChild.UpdateSettings(settings, profile); + _secondChild.UpdateSettings(settings, profile); + } + + // Method Description: + // - Returns nullptr if no children of this pane were the last pane to be focused, or the Pane that + // _was_ the last pane to be focused + // Return Value: + // - nullptr if no children were marked `_lastActive`, else returns the last active child + IPane ParentPane::GetActivePane() const + { + const auto first = _firstChild.GetActivePane(); + if (first != nullptr) + { + return first; + } + return _secondChild.GetActivePane(); + } + + // Method Description: + // - Recalculates and reapplies sizes of all descendant panes. + void ParentPane::Relayout() + { + ResizeContent(Root().ActualSize()); + } + + // Method Description: + // - Recursive function that focuses a pane with the given ID + // Arguments: + // - The ID of the pane we want to focus + void ParentPane::FocusPane(uint32_t id) + { + _firstChild.FocusPane(id); + _secondChild.FocusPane(id); + } + + // Method Description: + // - Focuses the first leaf of our first child, recursively. + void ParentPane::FocusFirstChild() + { + _firstChild.FocusFirstChild(); + } + + // Method Description: + // - Returns true is a pane which is a child of this pane that is actively focused + // Return Value: + // - true if the currently focused pane is one of this pane's descendants + bool ParentPane::HasFocusedChild() + { + return _firstChild.HasFocusedChild() || _secondChild.HasFocusedChild(); + } + + bool ParentPane::ContainsReadOnly() + { + return _firstChild.ContainsReadOnly() || _secondChild.ContainsReadOnly(); + } + + // Method Description: + // - Adds our children to the UI tree + // - Adds event handlers for our children + // - Animates our children + void ParentPane::InitializeChildren() + { + Root().Children().Append(_firstChild.try_as()); + Root().Children().Append(_secondChild.try_as()); + + _SetupChildEventHandlers(true); + _SetupChildEventHandlers(false); + } + + // Method Description: + // - Prepare this pane to be removed from the UI hierarchy by closing all controls + // and connections beneath it. + void ParentPane::Shutdown() + { + _firstChild.Shutdown(); + _secondChild.Shutdown(); + } + + // Method Description: + // - Recursively remove the "Active" state from any leaf descendants of this parent pane + void ParentPane::ClearActive() + { + _firstChild.ClearActive(); + _secondChild.ClearActive(); + } + + // Method Description: + // - Update the size of this pane. Resizes each of our columns so they have the + // same relative sizes, given the newSize. + // - Because we're just manually setting the row/column sizes in pixels, we have + // to be told our new size, we can't just use our own OnSized event, because + // that _won't fire when we get smaller_. + // Arguments: + // - newSize: the amount of space that this pane has to fill now. + void ParentPane::ResizeContent(const Size& newSize) + { + const auto width = newSize.Width; + const auto height = newSize.Height; + + _CreateRowColDefinitions(); + + if (_splitState == SplitState::Vertical) + { + const auto paneSizes = _CalcChildrenSizes(width); + + const Size firstSize{ paneSizes.first, height }; + const Size secondSize{ paneSizes.second, height }; + _firstChild.ResizeContent(firstSize); + _secondChild.ResizeContent(secondSize); + } + else if (_splitState == SplitState::Horizontal) + { + const auto paneSizes = _CalcChildrenSizes(height); + + const Size firstSize{ width, paneSizes.first }; + const Size secondSize{ width, paneSizes.second }; + _firstChild.ResizeContent(firstSize); + _secondChild.ResizeContent(secondSize); + } + } + + // Method Description: + // - Moves the separator between panes, as to resize each child on either size + // of the separator. Tries to move a separator in the given direction. The + // separator moved is the separator that's closest depth-wise to the + // currently focused pane, that's also in the correct direction to be moved. + // If there isn't such a separator, then this method returns false, as we + // couldn't handle the resize. + // Arguments: + // - direction: The direction to move the separator in. + // Return Value: + // - true if we or a child handled this resize request. + bool ParentPane::ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction) + { + // Check if either our first or second child is the currently focused leaf. + // If it is, and the requested resize direction matches our separator, then + // we're the pane that needs to adjust its separator. + // If our separator is the wrong direction, then we can't handle it. + const auto firstIsLeaf = _firstChild.try_as(); + const auto secondIsLeaf = _secondChild.try_as(); + const bool firstIsFocused = firstIsLeaf && firstIsLeaf.WasLastFocused(); + const bool secondIsFocused = secondIsLeaf && secondIsLeaf.WasLastFocused(); + if (firstIsFocused || secondIsFocused) + { + return _Resize(direction); + } + + // If neither of our children were the focused leaf, then recurse into + // our children and see if they can handle the resize. + // For each child, if it has a focused descendant, try having that child + // handle the resize. + // If the child wasn't able to handle the resize, it's possible that + // there were no descendants with a separator the correct direction. If + // our separator _is_ the correct direction, then we should be the pane + // to resize. Otherwise, just return false, as we couldn't handle it + // either. + const auto firstIsParent = _firstChild.try_as(); + if (firstIsParent) + { + if (firstIsParent->GetActivePane()) + { + return firstIsParent->ResizePane(direction) || _Resize(direction); + } + } + + const auto secondIsParent = _secondChild.try_as(); + if (secondIsParent) + { + if (secondIsParent->GetActivePane()) + { + return secondIsParent->ResizePane(direction) || _Resize(direction); + } + } + + return false; + } + + // Method Description: + // - Attempts to move focus to one of our children. If we have a focused child, + // we'll try to move the focus in the direction requested. + // - If there isn't a pane that exists as a child of this pane in the correct + // direction, we'll return false. This will indicate to our parent that they + // should try and move the focus themselves. In this way, the focus can move + // up and down the tree to the correct pane. + // - This method is _very_ similar to ResizePane. Both are trying to find the + // right separator to move (focus) in a direction. + // Arguments: + // - direction: The direction to move the focus in. + // Return Value: + // - true if we or a child handled this focus move request. + bool ParentPane::NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction) + { + // Check if either our first or second child is the currently focused leaf. + // If it is, and the requested move direction matches our separator, then + // we're the pane that needs to handle this focus move. + const auto firstIsLeaf = _firstChild.try_as(); + const auto secondIsLeaf = _secondChild.try_as(); + const bool firstIsFocused = firstIsLeaf && firstIsLeaf.WasLastFocused(); + const bool secondIsFocused = secondIsLeaf && secondIsLeaf.WasLastFocused(); + if (firstIsFocused || secondIsFocused) + { + return _NavigateFocus(direction); + } + + // If neither of our children were the focused leaf, then recurse into + // our children and see if they can handle the focus move. + // For each child, if it has a focused descendant, try having that child + // handle the focus move. + // If the child wasn't able to handle the focus move, it's possible that + // there were no descendants with a separator the correct direction. If + // our separator _is_ the correct direction, then we should be the pane + // to move focus into our other child. Otherwise, just return false, as + // we couldn't handle it either. + const auto firstIsParent = _firstChild.try_as(); + if (firstIsParent) + { + if (firstIsParent->GetActivePane()) + { + return firstIsParent->NavigateFocus(direction) || _NavigateFocus(direction); + } + } + + const auto secondIsParent = _secondChild.try_as(); + if (secondIsParent) + { + if (secondIsParent->GetActivePane()) + { + return secondIsParent->NavigateFocus(direction) || _NavigateFocus(direction); + } + } + + return false; + } + + // Method Description: + // - Adjusts given dimension (width or height) so that all descendant terminals + // align with their character grids as close as possible. Snaps to closes match + // (either upward or downward). Also makes sure to fit in minimal sizes of the panes. + // Arguments: + // - widthOrHeight: if true operates on width, otherwise on height + // - dimension: a dimension (width or height) to snap + // Return Value: + // - A value corresponding to the next closest snap size for this Pane, either upward or downward + float ParentPane::CalcSnappedDimensionSingle(const bool widthOrHeight, const float dimension) const + { + const auto [lower, higher] = CalcSnappedDimension(widthOrHeight, dimension); + return dimension - lower < higher - dimension ? lower : higher; + } + + uint32_t ParentPane::GetLeafPaneCount() const noexcept + { + return _firstChild.GetLeafPaneCount() + _secondChild.GetLeafPaneCount(); + } + + // Method Description: + // - Get the absolute minimum size that this pane can be resized to and still + // have 1x1 character visible in each of its children + // Return Value: + // - The minimum size that this pane can be resized to and still have a visible + // character. + Size ParentPane::GetMinSize() const + { + const auto firstSize = _firstChild.GetMinSize(); + const auto secondSize = _secondChild.GetMinSize(); + + const auto minWidth = _splitState == SplitState::Vertical ? + firstSize.Width + secondSize.Width : + std::max(firstSize.Width, secondSize.Width); + const auto minHeight = _splitState == SplitState::Horizontal ? + firstSize.Height + secondSize.Height : + std::max(firstSize.Height, secondSize.Height); + + return { minWidth, minHeight }; + } + + // Method Description: + // - Recursively attempt to "zoom" the given pane. When the pane is zoomed, it + // won't be displayed as part of the tab tree, instead it'll take up the full + // content of the tab. When we find the given pane, we'll need to remove it + // from the UI tree, so that the caller can re-add it. We'll also set some + // internal state, so the pane can display all of its borders. + // Arguments: + // - zoomedPane: This is the pane which we're attempting to zoom on. + void ParentPane::Maximize(IPane paneToZoom) + { + if (paneToZoom == _firstChild || paneToZoom == _secondChild) + { + // When we're zooming the pane, we'll need to remove it from our UI + // tree. Easy way: just remove both children. We'll re-attach both + // when we un-zoom. + Root().Children().Clear(); + } + + // Always recurse into both children. If the (un)zoomed pane was one of + // our direct children, we'll still want to update it's borders. + _firstChild.Maximize(paneToZoom); + _secondChild.Maximize(paneToZoom); + } + + // Method Description: + // - Recursively attempt to "un-zoom" the given pane. This does the opposite of + // ParentPane::Maximize. When we find the given pane, we should return the pane to our + // UI tree. We'll also clear the internal state, so the pane can display its + // borders correctly. + // - The caller should make sure to have removed the zoomed pane from the UI + // tree _before_ calling this. + // Arguments: + // - zoomedPane: This is the pane which we're attempting to un-zoom. + void ParentPane::Restore(IPane paneToUnzoom) + { + if (paneToUnzoom == _firstChild || paneToUnzoom == _secondChild) + { + // When we're un-zooming the pane, we'll need to re-add it to our UI + // tree where it originally belonged. easy way: just re-add both. + Root().Children().Clear(); + Root().Children().Append(_firstChild.try_as()); + Root().Children().Append(_secondChild.try_as()); + } + + // Always recurse into both children. If the (un)zoomed pane was one of + // our direct children, we'll still want to update it's borders. + _firstChild.Restore(paneToUnzoom); + _secondChild.Restore(paneToUnzoom); + } + + // Method Description: + // - This is a helper to determine which direction an "Automatic" split should + // happen in for a given pane, but without using the ActualWidth() and + // ActualHeight() methods. This is used during the initialization of the + // Terminal, when we could be processing many "split-pane" commands _before_ + // we've ever laid out the Terminal for the first time. When this happens, the + // Pane's don't have an actual size yet. However, we'd still like to figure + // out how to do an "auto" split when these Panes are all laid out. + // - This method assumes that the Pane we're attempting to split is `target`, + // and this method should be called on the root of a tree of Panes. + // - We'll walk down the tree attempting to find `target`. As we traverse the + // tree, we'll reduce the size passed to each subsequent recursive call. The + // size passed to this method represents how much space this Pane _will_ have + // to use. + // - Since this pane is a parent, calculate how much space our children will be + // able to use, and recurse into them. + // Arguments: + // - target: The Pane we're attempting to split. + // - availableSpace: The theoretical space that's available for this pane to be able to split. + // Return Value: + // - nullopt if `target` is not this pane or a child of this pane, otherwise the + // SplitState that `target` would use for an `Automatic` split given + // `availableSpace` + IReference ParentPane::PreCalculateAutoSplit(const IPane target, + const winrt::Windows::Foundation::Size availableSpace) const + { + const bool isVerticalSplit = _splitState == SplitState::Vertical; + const float firstWidth = isVerticalSplit ? (availableSpace.Width * _desiredSplitPosition) : availableSpace.Width; + const float secondWidth = isVerticalSplit ? (availableSpace.Width - firstWidth) : availableSpace.Width; + const float firstHeight = !isVerticalSplit ? (availableSpace.Height * _desiredSplitPosition) : availableSpace.Height; + const float secondHeight = !isVerticalSplit ? (availableSpace.Height - firstHeight) : availableSpace.Height; + + const auto firstResult = _firstChild.PreCalculateAutoSplit(target, { firstWidth, firstHeight }); + return firstResult ? firstResult : _secondChild.PreCalculateAutoSplit(target, { secondWidth, secondHeight }); + } + + // Method Description: + // - This is a helper to determine if a given Pane can be split, but without + // using the ActualWidth() and ActualHeight() methods. This is used during + // processing of many "split-pane" commands, which could happen _before_ we've + // laid out a Pane for the first time. When this happens, the Pane's don't + // have an actual size yet. However, we'd still like to figure out if the pane + // could be split, once they're all laid out. + // - This method assumes that the Pane we're attempting to split is `target`, + // and this method should be called on the root of a tree of Panes. + // - We'll walk down the tree attempting to find `target`. As we traverse the + // tree, we'll reduce the size passed to each subsequent recursive call. The + // size passed to this method represents how much space this Pane _will_ have + // to use. + // - Since this pane is a parent, calculate how much space our children will be + // able to use, and recurse into them. + // Arguments: + // - target: The Pane we're attempting to split. + // - splitType: The direction we're attempting to split in. + // - availableSpace: The theoretical space that's available for this pane to be able to split. + // Return Value: + // - nullopt if `target` is not a child of this pane, otherwise + // true iff we could split this pane, given `availableSpace` + // Note: + // - This method is highly similar to PreCalculateAutoSplit + IReference ParentPane::PreCalculateCanSplit(const IPane target, + SplitState splitType, + const float splitSize, + const winrt::Windows::Foundation::Size availableSpace) const + { + const bool isVerticalSplit = _splitState == SplitState::Vertical; + const float firstWidth = isVerticalSplit ? + (availableSpace.Width * _desiredSplitPosition) - PaneBorderSize : + availableSpace.Width; + const float secondWidth = isVerticalSplit ? + (availableSpace.Width - firstWidth) - PaneBorderSize : + availableSpace.Width; + const float firstHeight = !isVerticalSplit ? + (availableSpace.Height * _desiredSplitPosition) - PaneBorderSize : + availableSpace.Height; + const float secondHeight = !isVerticalSplit ? + (availableSpace.Height - firstHeight) - PaneBorderSize : + availableSpace.Height; + + const auto firstResult = _firstChild.PreCalculateCanSplit(target, splitType, splitSize, { firstWidth, firstHeight }); + return firstResult ? firstResult : _secondChild.PreCalculateCanSplit(target, splitType, splitSize, { secondWidth, secondHeight }); + } + + IPane ParentPane::FindFirstLeaf() + { + return _firstChild.FindFirstLeaf(); + } + + void ParentPane::PropagateToLeavesOnEdge(const ResizeDirection& edge, std::function action) + { + if (DirectionMatchesSplit(edge, _splitState)) + { + const auto& adjacentChild = (_splitState == SplitState::Vertical && edge == ResizeDirection::Left || + _splitState == SplitState::Horizontal && edge == ResizeDirection::Up) ? + _firstChild : + _secondChild; + if (auto adjChildAsLeaf = adjacentChild.try_as()) + { + action(adjChildAsLeaf); + } + else + { + auto adjChildAsParentImpl = winrt::get_self(adjacentChild); + adjChildAsParentImpl->PropagateToLeavesOnEdge(edge, action); + } + } + else + { + if (auto firstChildAsLeaf = _firstChild.try_as()) + { + action(firstChildAsLeaf); + } + else + { + auto firstChildAsParentImpl = winrt::get_self(_firstChild); + firstChildAsParentImpl->PropagateToLeavesOnEdge(edge, action); + } + if (auto secondChildAsLeaf = _secondChild.try_as()) + { + action(secondChildAsLeaf); + } + else + { + auto secondChildAsParentImpl = winrt::get_self(_secondChild); + secondChildAsParentImpl->PropagateToLeavesOnEdge(edge, action); + } + } + } + + // Method Description: + // - Sets up row/column definitions for this pane. There are three total + // row/cols. The middle one is for the separator. The first and third are for + // each of the child panes, and are given a size in pixels, based off the + // available space, and the percent of the space they respectively consume, + // which is stored in _desiredSplitPosition + void ParentPane::_CreateRowColDefinitions() + { + const auto first = _desiredSplitPosition * 100.0f; + const auto second = 100.0f - first; + if (_splitState == SplitState::Vertical) + { + Root().ColumnDefinitions().Clear(); + + // Create two columns in this grid: one for each pane + + auto firstColDef = Controls::ColumnDefinition(); + firstColDef.Width(GridLengthHelper::FromValueAndType(first, GridUnitType::Star)); + + auto secondColDef = Controls::ColumnDefinition(); + secondColDef.Width(GridLengthHelper::FromValueAndType(second, GridUnitType::Star)); + + Root().ColumnDefinitions().Append(firstColDef); + Root().ColumnDefinitions().Append(secondColDef); + } + else if (_splitState == SplitState::Horizontal) + { + Root().RowDefinitions().Clear(); + + // Create two rows in this grid: one for each pane + + auto firstRowDef = Controls::RowDefinition(); + firstRowDef.Height(GridLengthHelper::FromValueAndType(first, GridUnitType::Star)); + + auto secondRowDef = Controls::RowDefinition(); + secondRowDef.Height(GridLengthHelper::FromValueAndType(second, GridUnitType::Star)); + + Root().RowDefinitions().Append(firstRowDef); + Root().RowDefinitions().Append(secondRowDef); + } + } + + // Method Description: + // - Adjust our child percentages to increase the size of one of our children + // and decrease the size of the other. + // - Adjusts the separation amount by 5% + // - Does nothing if the direction doesn't match our current split direction + // Arguments: + // - direction: the direction to move our separator. If it's down or right, + // we'll be increasing the size of the first of our children. Else, we'll be + // decreasing the size of our first child. + // Return Value: + // - false if we couldn't resize this pane in the given direction, else true. + bool ParentPane::_Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction) + { + if (!DirectionMatchesSplit(direction, _splitState)) + { + return false; + } + + float amount = .05f; + if (direction == ResizeDirection::Right || direction == ResizeDirection::Down) + { + amount = -amount; + } + + // Make sure we're not making a pane explode here by resizing it to 0 characters. + const bool changeWidth = _splitState == SplitState::Vertical; + + const Size actualSize{ gsl::narrow_cast(Root().ActualWidth()), + gsl::narrow_cast(Root().ActualHeight()) }; + // actualDimension is the size in DIPs of this pane in the direction we're + // resizing. + const auto actualDimension = changeWidth ? actualSize.Width : actualSize.Height; + + _desiredSplitPosition = _ClampSplitPosition(changeWidth, _desiredSplitPosition - amount, actualDimension); + + // Resize our columns to match the new percentages. + ResizeContent(actualSize); + + return true; + } + + // Method Description: + // - Attempts to handle moving focus to one of our children. If our split + // direction isn't appropriate for the move direction, then we'll return + // false, to try and let our parent handle the move. If our child we'd move + // focus to is already focused, we'll also return false, to again let our + // parent try and handle the focus movement. + // Arguments: + // - direction: The direction to move the focus in. + // Return Value: + // - true if we handled this focus move request. + bool ParentPane::_NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction) + { + if (!DirectionMatchesSplit(direction, _splitState)) + { + return false; + } + + const bool focusSecond = (direction == FocusDirection::Right) || (direction == FocusDirection::Down); + + const auto newlyFocusedChild = focusSecond ? _secondChild : _firstChild; + + // If the child we want to move focus to is _already_ focused, return false, + // to try and let our parent figure it out. + if (newlyFocusedChild.HasFocusedChild()) + { + return false; + } + + // Transfer focus to our child. + newlyFocusedChild.FocusFirstChild(); + + return true; + } + + // Method Description: + // - Helper to handle when our children's layouts have updated + // - When a child's layout updates, we revoke the revoker to only let this succeed once, + // and then we check if both children's layouts have been updated, if so then we go ahead + // and initialize them + // Arguments: + // - isFirstChild: which child's layout got updated + void ParentPane::_ChildrenLayoutUpdatedHelper(const bool isFirstChild) + { + if (isFirstChild) + { + _firstLayoutUpdated = true; + _firstLayoutRevoker.revoke(); + } + else + { + _secondLayoutUpdated = true; + _secondLayoutRevoker.revoke(); + } + + if (_firstLayoutUpdated && _secondLayoutUpdated) + { + // Once both children have their sizes, we can initialize them + _SetupEntranceAnimation(); + } + } + + // Method Description: + // - Create a pair of animations when a new control enters this pane. This + // should _ONLY_ be called in InitializeChildren, AFTER the first and second child panes + // have been set up + void ParentPane::_SetupEntranceAnimation() + { + // This will query if animations are enabled via the "Show animations in + // Windows" setting in the OS + winrt::Windows::UI::ViewManagement::UISettings uiSettings; + const auto animationsEnabledInOS = uiSettings.AnimationsEnabled(); + const auto animationsEnabledInApp = Media::Animation::Timeline::AllowDependentAnimations(); + + const bool splitWidth = _splitState == SplitState::Vertical; + const auto totalSize = splitWidth ? ActualWidth() : ActualHeight(); + + // There's a chance that we're in startup and so by the time we get here one of our children is + // actually a parent, don't try to animate in that case + if (!(_firstChild.try_as() && _secondChild.try_as())) + { + return; + } + + // If we don't have a size yet, it's likely that we're in startup, or we're + // being executed as a sequence of actions. In that case, just skip the + // animation. + if (totalSize <= 0 || !animationsEnabledInOS || !animationsEnabledInApp) + { + return; + } + + const auto [firstSize, secondSize] = _CalcChildrenSizes(::base::saturated_cast(totalSize)); + + // This is safe to capture this, because it's only being called in the + // context of this method (not on another thread) + auto setupAnimation = [=](const auto& size, const bool isFirstChild) { + auto child = isFirstChild ? _firstChild : _secondChild; + auto childGrid = child.GetRootElement(); + auto control = child.try_as().TerminalControl(); + // Build up our animation: + // * it'll take as long as our duration (200ms) + // * it'll change the value of our property from 0 to secondSize + // * it'll animate that value using a quadratic function (like f(t) = t^2) + // * IMPORTANT! We'll manually tell the animation that "yes we know what + // we're doing, we want an animation here." + Media::Animation::DoubleAnimation animation{}; + animation.Duration(AnimationDuration); + if (isFirstChild) + { + // If we're animating the first pane, the size should decrease, from + // the full size down to the given size. + animation.From(totalSize); + animation.To(size); + } + else + { + // Otherwise, we want to show the pane getting larger, so animate + // from 0 to the requested size. + animation.From(0.0); + animation.To(size); + } + animation.EasingFunction(Media::Animation::QuadraticEase{}); + animation.EnableDependentAnimation(true); + + // Now we're going to set up the Storyboard. This is a unit that uses the + // Animation from above, and actually applies it to a property. + // * we'll set it up for the same duration as the animation we have + // * Apply the animation to the grid of the new pane we're adding to the tree. + // * apply the animation to the Width or Height property. + Media::Animation::Storyboard s; + s.Duration(AnimationDuration); + s.Children().Append(animation); + s.SetTarget(animation, childGrid); + s.SetTargetProperty(animation, splitWidth ? L"Width" : L"Height"); + + // BE TRICKY: + // We're animating the width or height of our child pane's grid. + // + // We DON'T want to change the size of the control itself, because the + // terminal has to reflow the buffer every time the control changes size. So + // what we're going to do there is manually set the control's size to how + // big we _actually know_ the control will be. + // + // We're also going to be changing alignment of our child pane and the + // control. This way, we'll be able to have the control stick to the inside + // of the child pane's grid (the side that's moving), while we also have the + // pane's grid stick to "outside" of the grid (the side that's not moving) + if (splitWidth) + { + // If we're animating the first child, then stick to the top/left of + // the parent pane, otherwise use the bottom/right. This is always + // the "outside" of the parent pane. + childGrid.HorizontalAlignment(isFirstChild ? HorizontalAlignment::Left : HorizontalAlignment::Right); + control.HorizontalAlignment(HorizontalAlignment::Left); + control.Width(isFirstChild ? totalSize : size); + + // When the animation is completed, undo the trickiness from before, to + // restore the controls to the behavior they'd usually have. + animation.Completed([childGrid, control](auto&&, auto&&) { + control.Width(NAN); + childGrid.Width(NAN); + childGrid.HorizontalAlignment(HorizontalAlignment::Stretch); + control.HorizontalAlignment(HorizontalAlignment::Stretch); + }); + } + else + { + // If we're animating the first child, then stick to the top/left of + // the parent pane, otherwise use the bottom/right. This is always + // the "outside" of the parent pane. + childGrid.VerticalAlignment(isFirstChild ? VerticalAlignment::Top : VerticalAlignment::Bottom); + control.VerticalAlignment(VerticalAlignment::Top); + control.Height(isFirstChild ? totalSize : size); + + // When the animation is completed, undo the trickiness from before, to + // restore the controls to the behavior they'd usually have. + animation.Completed([childGrid, control](auto&&, auto&&) { + control.Height(NAN); + childGrid.Height(NAN); + childGrid.VerticalAlignment(VerticalAlignment::Stretch); + control.VerticalAlignment(VerticalAlignment::Stretch); + }); + } + + // Start the animation. + s.Begin(); + }; + + // TODO: GH#7365 - animating the first child right now doesn't _really_ do + // anything. We could do better though. + setupAnimation(firstSize, true); + setupAnimation(secondSize, false); + } + + // Method Description: + // - Closes one of our children. In doing so, emit an event containing the + // remaining child, so that whoever is listening (either our parent or + // the hosting tab if we were the root) will replace us with the remaining child + // Arguments: + // - closeFirst: if true, the first child should be closed, and the second + // should be preserved, and vice-versa for false. + void ParentPane::_CloseChild(const bool closeFirst) + { + // The closed child must always be a leaf. + const auto closedChild = (closeFirst ? _firstChild.try_as() : _secondChild.try_as()); + THROW_HR_IF_NULL(E_FAIL, closedChild); + + const auto remainingChild = closeFirst ? _secondChild : _firstChild; + + // Detach all the controls from our grid, so they can be attached later. + Root().Children().Clear(); + + const auto closedChildDir = (_splitState == SplitState::Vertical) ? + (closeFirst ? ResizeDirection::Left : ResizeDirection::Right) : + (closeFirst ? ResizeDirection::Up : ResizeDirection::Down); + + if (const auto remainingAsLeaf = remainingChild.try_as()) + { + // If our remaining child is a leaf, tell it to update its border now that its neighbour has closed + remainingAsLeaf.UpdateBorderWithClosedNeighbor(closedChild, closedChildDir); + } + else + { + // If our remaining child is a parent, we need to propagate the update border call to all leaves that shared + // an edge with the closed child + const auto remainingAsParent = remainingChild.try_as(); + remainingAsParent->PropagateToLeavesOnEdge(closedChildDir, [&](TerminalApp::LeafPane paneOnEdge) { + paneOnEdge.UpdateBorderWithClosedNeighbor(closedChild, closedChildDir); + }); + } + + // If the closed child was last active, make sure to set a leaf in our remaining child + // as last active before we collapse (there should always be exactly 1 active leaf) + if (closedChild.WasLastFocused()) + { + closedChild.ClearActive(); + auto& remainingChild = closeFirst ? _secondChild : _firstChild; + remainingChild.FindFirstLeaf().try_as().SetActive(); + } + + // Make sure to only fire off this event _after_ we have set the new active pane, because this event + // might cause the tab content to change which will fire off a property changed event which eventually + // results in the tab trying to access the active terminal control, which requires a valid active pane + _PaneTypeChangedHandlers(nullptr, remainingChild); + } + + winrt::fire_and_forget ParentPane::_CloseChildRoutine(const bool closeFirst) + { + auto weakThis = get_weak(); + co_await winrt::resume_foreground(Root().Dispatcher()); + + if (auto pane{ weakThis.get() }) + { + // This will query if animations are enabled via the "Show animations in + // Windows" setting in the OS + winrt::Windows::UI::ViewManagement::UISettings uiSettings; + const auto animationsEnabledInOS = uiSettings.AnimationsEnabled(); + const auto animationsEnabledInApp = Media::Animation::Timeline::AllowDependentAnimations(); + + // GH#7252: If either child is zoomed, just skip the animation. It won't work. + const bool eitherChildZoomed = false; + //const bool eitherChildZoomed = pane->_firstChild->_zoomed || pane->_secondChild->_zoomed; + // If animations are disabled, just skip this and go straight to + // _CloseChild. Curiously, the pane opening animation doesn't need this, + // and will skip straight to Completed when animations are disabled, but + // this one doesn't seem to. + if (!animationsEnabledInOS || !animationsEnabledInApp || eitherChildZoomed) + { + _CloseChild(closeFirst); + co_return; + } + + // Setup the animation + + auto removedChild = closeFirst ? _firstChild : _secondChild; + auto remainingChild = closeFirst ? _secondChild : _firstChild; + const bool splitWidth = _splitState == SplitState::Vertical; + const auto totalSize = splitWidth ? Root().ActualWidth() : Root().ActualHeight(); + + Size removedOriginalSize; + Size remainingOriginalSize; + + if (const auto removedAsLeaf = removedChild.try_as()) + { + removedOriginalSize = Size{ ::base::saturated_cast(removedAsLeaf.ActualWidth()), + ::base::saturated_cast(removedAsLeaf.ActualHeight()) }; + } + else + { + const auto removedAsParent = removedChild.try_as(); + removedOriginalSize = Size{ ::base::saturated_cast(removedAsParent.ActualWidth()), + ::base::saturated_cast(removedAsParent.ActualHeight()) }; + } + + if (const auto remainingAsLeaf = remainingChild.try_as()) + { + remainingOriginalSize = Size{ ::base::saturated_cast(remainingAsLeaf.ActualWidth()), + ::base::saturated_cast(remainingAsLeaf.ActualHeight()) }; + } + else + { + const auto remainingAsParent = remainingChild.try_as(); + remainingOriginalSize = Size{ ::base::saturated_cast(remainingAsParent.ActualWidth()), + ::base::saturated_cast(remainingAsParent.ActualHeight()) }; + } + + // Remove both children from the grid + Root().Children().Clear(); + // Add the remaining child back to the grid, in the right place. + Root().Children().Append(remainingChild.try_as()); + if (_splitState == SplitState::Vertical) + { + Controls::Grid::SetColumn(remainingChild.try_as(), closeFirst ? 1 : 0); + } + else if (_splitState == SplitState::Horizontal) + { + Controls::Grid::SetRow(remainingChild.try_as(), closeFirst ? 1 : 0); + } + + // Create the dummy grid. This grid will be the one we actually animate, + // in the place of the closed pane. + Controls::Grid dummyGrid; + + dummyGrid.Background(s_unfocusedBorderBrush); + + // It should be the size of the closed pane. + dummyGrid.Width(removedOriginalSize.Width); + dummyGrid.Height(removedOriginalSize.Height); + // Put it where the removed child is + if (_splitState == SplitState::Vertical) + { + Controls::Grid::SetColumn(dummyGrid, closeFirst ? 0 : 1); + } + else if (_splitState == SplitState::Horizontal) + { + Controls::Grid::SetRow(dummyGrid, closeFirst ? 0 : 1); + } + // Add it to the tree + Root().Children().Append(dummyGrid); + + // Set up the rows/cols as auto/auto, so they'll only use the size of + // the elements in the grid. + // + // * For the closed pane, we want to make that row/col "auto" sized, so + // it takes up as much space as is available. + // * For the remaining pane, we'll make that row/col "*" sized, so it + // takes all the remaining space. As the dummy grid is resized down, + // the remaining pane will expand to take the rest of the space. + Root().ColumnDefinitions().Clear(); + Root().RowDefinitions().Clear(); + if (_splitState == SplitState::Vertical) + { + auto firstColDef = Controls::ColumnDefinition(); + auto secondColDef = Controls::ColumnDefinition(); + firstColDef.Width(!closeFirst ? GridLengthHelper::FromValueAndType(1, GridUnitType::Star) : GridLengthHelper::Auto()); + secondColDef.Width(closeFirst ? GridLengthHelper::FromValueAndType(1, GridUnitType::Star) : GridLengthHelper::Auto()); + Root().ColumnDefinitions().Append(firstColDef); + Root().ColumnDefinitions().Append(secondColDef); + } + else if (_splitState == SplitState::Horizontal) + { + auto firstRowDef = Controls::RowDefinition(); + auto secondRowDef = Controls::RowDefinition(); + firstRowDef.Height(!closeFirst ? GridLengthHelper::FromValueAndType(1, GridUnitType::Star) : GridLengthHelper::Auto()); + secondRowDef.Height(closeFirst ? GridLengthHelper::FromValueAndType(1, GridUnitType::Star) : GridLengthHelper::Auto()); + Root().RowDefinitions().Append(firstRowDef); + Root().RowDefinitions().Append(secondRowDef); + } + + // Animate the dummy grid from its current size down to 0 + Media::Animation::DoubleAnimation animation{}; + animation.Duration(AnimationDuration); + animation.From(splitWidth ? removedOriginalSize.Width : removedOriginalSize.Height); + animation.To(0.0); + // This easing is the same as the entrance animation. + animation.EasingFunction(Media::Animation::QuadraticEase{}); + animation.EnableDependentAnimation(true); + + Media::Animation::Storyboard s; + s.Duration(AnimationDuration); + s.Children().Append(animation); + s.SetTarget(animation, dummyGrid); + s.SetTargetProperty(animation, splitWidth ? L"Width" : L"Height"); + + // Start the animation. + s.Begin(); + + auto strongThis{ get_strong() }; + // When the animation is completed, reparent the child's content up to + // us, and remove the child nodes from the tree. + animation.Completed([=](auto&&, auto&&) { + // We don't need to manually undo any of the above trickiness. + // We're going to re-parent the child's content into us anyways + strongThis->_CloseChild(closeFirst); + }); + } + } + + // Method Description: + // - Adds event handlers to our children + // - For child leaves, we handle their Closed event + // - For all children, we listen to their type changed events + void ParentPane::_SetupChildEventHandlers(const bool isFirstChild) + { + auto& child = isFirstChild ? _firstChild : _secondChild; + auto& closedToken = isFirstChild ? _firstClosedToken : _secondClosedToken; + auto& typeChangedToken = isFirstChild ? _firstTypeChangedToken : _secondTypeChangedToken; + + if (const auto childAsLeaf = child.try_as()) + { + // When our child is a leaf and got closed, we close it + const auto childImpl = winrt::get_self(child); + + closedToken = childImpl->Closed([=](auto&& /*s*/, auto&& /*a*/) { + // Unsubscribe from events of both our children, as we ourself will also + // get closed when our child does. + _RemoveAllChildEventHandlers(false); + _RemoveAllChildEventHandlers(true); + _CloseChildRoutine(isFirstChild); + }); + } + + // When our child is a leaf and gets split, it produces a new parent pane that contains + // both itself and its new leaf neighbor. We then replace that child with the new parent pane. + + // When our child is a parent and one of its children got closed (and so the parent collapses), + // we take in its remaining, orphaned child as our own. + + // Either way, the event handling is the same - update the event handlers and update the content + // of the appropriate root + typeChangedToken = child.PaneTypeChanged([=](auto&& /*s*/, IPane newPane) { + _OnChildSplitOrCollapse(isFirstChild, newPane); + }); + } + + void ParentPane::_RemoveAllChildEventHandlers(const bool isFirstChild) + { + const auto child = isFirstChild ? _firstChild : _secondChild; + const auto closedToken = isFirstChild ? _firstClosedToken : _secondClosedToken; + const auto typeChangedToken = isFirstChild ? _firstTypeChangedToken : _secondTypeChangedToken; + + if (const auto childAsLeaf = child.try_as()) + { + const auto childImpl = winrt::get_self(child); + childImpl->Closed(closedToken); + } + child.PaneTypeChanged(typeChangedToken); + } + + void ParentPane::_OnChildSplitOrCollapse(const bool isFirstChild, IPane newChild) + { + // Unsubscribe from all the events of the child. + _RemoveAllChildEventHandlers(isFirstChild); + + // check whether we need to move focus to the newChild after + // we are done modifying the UI tree + bool moveFocusAfter{ false }; + const auto childToReplace = isFirstChild ? _firstChild : _secondChild; + + // we only need to move the focus if a parent pane is collapsing and + // one of its leaves had focus + if (childToReplace.try_as()) + { + moveFocusAfter = childToReplace.HasFocusedChild(); + } + + (isFirstChild ? _firstChild : _secondChild) = newChild; + + const auto remainingChild = isFirstChild ? _secondChild : _firstChild; + + Root().Children().Clear(); + _GridLayoutHelper(); + Root().Children().Append(_firstChild.try_as()); + Root().Children().Append(_secondChild.try_as()); + + // Setup events appropriate for the new child + _SetupChildEventHandlers(isFirstChild); + + if (moveFocusAfter) + { + newChild.FocusFirstChild(); + } + } + + void ParentPane::_GridLayoutHelper() const noexcept + { + Controls::Grid::SetColumn(_firstChild.try_as(), 0); + Controls::Grid::SetRow(_firstChild.try_as(), 0); + + Controls::Grid::SetColumn(_secondChild.try_as(), _splitState == SplitState::Vertical ? 1 : 0); + Controls::Grid::SetRow(_secondChild.try_as(), _splitState == SplitState::Horizontal ? 1 : 0); + } + + // Method Description: + // - Gets the size in pixels of each of our children, given the full size they + // should fill. Since these children own their own separators (borders), this + // size is their portion of our _entire_ size. If specified size is lower than + // required then children will be of minimum size. Snaps first child to grid + // but not the second. + // Arguments: + // - fullSize: the amount of space in pixels that should be filled by our + // children and their separators. Can be arbitrarily low. + // Return Value: + // - a pair with the size of our first child and the size of our second child, + // respectively. + std::pair ParentPane::_CalcChildrenSizes(const float fullSize) const + { + const auto widthOrHeight = _splitState == SplitState::Vertical; + const auto snappedSizes = _CalcSnappedChildrenSizes(widthOrHeight, fullSize).lower; + + // Keep the first pane snapped and give the second pane all remaining size + return { + snappedSizes.first, + fullSize - snappedSizes.first + }; + } + + // Method Description: + // - Gets the size in pixels of each of our children, given the full size they should + // fill. Each child is snapped to char grid as close as possible. If called multiple + // times with fullSize argument growing, then both returned sizes are guaranteed to be + // non-decreasing (it's a monotonically increasing function). This is important so that + // user doesn't get any pane shrank when they actually expand the window or parent pane. + // That is also required by the layout algorithm. + // Arguments: + // - widthOrHeight: if true, operates on width, otherwise on height. + // - fullSize: the amount of space in pixels that should be filled by our children and + // their separator. Can be arbitrarily low. + // Return Value: + // - a structure holding the result of this calculation. The 'lower' field represents the + // children sizes that would fit in the fullSize, but might (and usually do) not fill it + // completely. The 'higher' field represents the size of the children if they slightly exceed + // the fullSize, but are snapped. If the children can be snapped and also exactly match + // the fullSize, then both this fields have the same value that represent this situation. + ParentPane::SnapChildrenSizeResult ParentPane::_CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const + { + auto sizeTree = _CreateMinSizeTree(widthOrHeight); + LayoutSizeNode lastSizeTree{ sizeTree }; + + while (sizeTree.size < fullSize) + { + lastSizeTree = sizeTree; + _AdvanceSnappedDimension(widthOrHeight, sizeTree); + + if (sizeTree.size == fullSize) + { + // If we just hit exactly the requested value, then just return the + // current state of children. + return { { sizeTree.firstChild->size, sizeTree.secondChild->size }, + { sizeTree.firstChild->size, sizeTree.secondChild->size } }; + } + } + + // We exceeded the requested size in the loop above, so lastSizeTree will have + // the last good sizes (so that children fit in) and sizeTree has the next possible + // snapped sizes. Return them as lower and higher snap possibilities. + return { { lastSizeTree.firstChild->size, lastSizeTree.secondChild->size }, + { sizeTree.firstChild->size, sizeTree.secondChild->size } }; + } + + // Method Description: + // - Adjusts given dimension (width or height) so that all descendant terminals + // align with their character grids as close as possible. Also makes sure to + // fit in minimal sizes of the panes. + // Arguments: + // - widthOrHeight: if true operates on width, otherwise on height + // - dimension: a dimension (width or height) to be snapped + // Return Value: + // - pair of floats, where first value is the size snapped downward (not greater then + // requested size) and second is the size snapped upward (not lower than requested size). + // If requested size is already snapped, then both returned values equal this value. + SnapSizeResult ParentPane::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const + { + if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) + { + // If we're resizing along separator axis, snap to the closest possibility + // given by our children panes. + + const auto firstSnapped = _firstChild.CalcSnappedDimension(widthOrHeight, dimension); + const auto secondSnapped = _secondChild.CalcSnappedDimension(widthOrHeight, dimension); + return { + std::max(firstSnapped.lower, secondSnapped.lower), + std::min(firstSnapped.higher, secondSnapped.higher) + }; + } + else + { + // If we're resizing perpendicularly to separator axis, calculate the sizes + // of child panes that would fit the given size. We use same algorithm that + // is used for real resize routine, but exclude the remaining empty space that + // would appear after the second pane. This will be the 'downward' snap possibility, + // while the 'upward' will be given as a side product of the layout function. + + const auto childSizes = _CalcSnappedChildrenSizes(widthOrHeight, dimension); + return { + childSizes.lower.first + childSizes.lower.second, + childSizes.higher.first + childSizes.higher.second + }; + } + } + + // Method Description: + // - Increases size of given LayoutSizeNode to match next possible 'snap'. In case of leaf + // pane this means the next cell of the terminal. Otherwise it means that one of its children + // advances (recursively). It expects the given node and its descendants to have either + // already snapped or minimum size. + // Arguments: + // - widthOrHeight: if true operates on width, otherwise on height. + // - sizeNode: a layout size node that corresponds to this pane. + void ParentPane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const + { + // We're a parent pane, so we have to advance dimension of our children panes. In + // fact, we advance only one child (chosen later) to keep the growth fine-grained. + + // To choose which child pane to advance, we actually need to know their advanced sizes + // in advance (oh), to see which one would 'fit' better. Often, this is already cached + // by the previous invocation of this function in nextFirstChild and nextSecondChild + // fields of given node. If not, we need to calculate them now. + if (sizeNode.nextFirstChild == nullptr) + { + sizeNode.nextFirstChild = std::make_unique(*sizeNode.firstChild); + + if (auto firstChildAsLeaf = _firstChild.try_as()) + { + if (sizeNode.nextFirstChild->isMinimumSize) + { + sizeNode.nextFirstChild->size = _firstChild.CalcSnappedDimension(widthOrHeight, sizeNode.nextFirstChild->size + 1).higher; + } + else + { + const auto cellSize = firstChildAsLeaf.TerminalControl().CharacterDimensions(); + sizeNode.nextFirstChild->size += widthOrHeight ? cellSize.Width : cellSize.Height; + } + } + else + { + auto firstChildAsParentImpl = winrt::get_self(_firstChild); + firstChildAsParentImpl->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); + } + } + if (sizeNode.nextSecondChild == nullptr) + { + sizeNode.nextSecondChild = std::make_unique(*sizeNode.secondChild); + + if (auto secondChildAsLeaf = _secondChild.try_as()) + { + if (sizeNode.nextSecondChild->isMinimumSize) + { + sizeNode.nextSecondChild->size = _secondChild.CalcSnappedDimension(widthOrHeight, sizeNode.nextSecondChild->size + 1).higher; + } + else + { + const auto cellSize = secondChildAsLeaf.TerminalControl().CharacterDimensions(); + sizeNode.nextSecondChild->size += widthOrHeight ? cellSize.Width : cellSize.Height; + } + } + else + { + auto secondChildAsParentImpl = winrt::get_self(_secondChild); + secondChildAsParentImpl->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); + } + } + + const auto nextFirstSize = sizeNode.nextFirstChild->size; + const auto nextSecondSize = sizeNode.nextSecondChild->size; + + // Choose which child pane to advance. + bool advanceFirstOrSecond; + if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) + { + // If we're growing along separator axis, choose the child that + // wants to be smaller than the other, so that the resulting size + // will be the smallest. + advanceFirstOrSecond = nextFirstSize < nextSecondSize; + } + else + { + // If we're growing perpendicularly to separator axis, choose a + // child so that their size ratio is closer to that we're trying + // to maintain (this is, the relative separator position is closer + // to the _desiredSplitPosition field). + + const auto firstSize = sizeNode.firstChild->size; + const auto secondSize = sizeNode.secondChild->size; + + // Because we rely on equality check, these calculations have to be + // immune to floating point errors. In common situation where both panes + // have the same character sizes and _desiredSplitPosition is 0.5 (or + // some simple fraction) both ratios will often be the same, and if so + // we always take the left child. It could be right as well, but it's + // important that it's consistent: that it would always go + // 1 -> 2 -> 1 -> 2 -> 1 -> 2 and not like 1 -> 1 -> 2 -> 2 -> 2 -> 1 + // which would look silly to the user but which occur if there was + // a non-floating-point-safe math. + const auto deviation1 = nextFirstSize - (nextFirstSize + secondSize) * _desiredSplitPosition; + const auto deviation2 = -1 * (firstSize - (firstSize + nextSecondSize) * _desiredSplitPosition); + advanceFirstOrSecond = deviation1 <= deviation2; + } + + // Here we advance one of our children. Because we already know the appropriate + // (advanced) size that given child would need to have, we simply assign that size + // to it. We then advance its 'next*' size (nextFirstChild or nextSecondChild) so + // the invariant holds (as it will likely be used by the next invocation of this + // function). The other child's next* size remains unchanged because its size + // haven't changed either. + if (advanceFirstOrSecond) + { + *sizeNode.firstChild = *sizeNode.nextFirstChild; + if (auto firstChildAsLeaf = _firstChild.try_as()) + { + if (sizeNode.nextFirstChild->isMinimumSize) + { + sizeNode.nextFirstChild->size = _firstChild.CalcSnappedDimension(widthOrHeight, sizeNode.nextFirstChild->size + 1).higher; + } + else + { + const auto cellSize = firstChildAsLeaf.TerminalControl().CharacterDimensions(); + sizeNode.nextFirstChild->size += widthOrHeight ? cellSize.Width : cellSize.Height; + } + } + else + { + auto firstChildAsParentImpl = winrt::get_self(_firstChild); + firstChildAsParentImpl->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); + } + } + else + { + *sizeNode.secondChild = *sizeNode.nextSecondChild; + if (auto secondChildAsLeaf = _secondChild.try_as()) + { + if (sizeNode.nextSecondChild->isMinimumSize) + { + sizeNode.nextSecondChild->size = _secondChild.CalcSnappedDimension(widthOrHeight, sizeNode.nextSecondChild->size + 1).higher; + } + else + { + const auto cellSize = secondChildAsLeaf.TerminalControl().CharacterDimensions(); + sizeNode.nextSecondChild->size += widthOrHeight ? cellSize.Width : cellSize.Height; + } + } + else + { + auto secondChildAsParentImpl = winrt::get_self(_secondChild); + secondChildAsParentImpl->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); + } + } + + // Since the size of one of our children has changed we need to update our size as well. + if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) + { + sizeNode.size = std::max(sizeNode.firstChild->size, sizeNode.secondChild->size); + } + else + { + sizeNode.size = sizeNode.firstChild->size + sizeNode.secondChild->size; + } + + // Because we have grown, we're certainly no longer of our + // minimal size (if we've ever been). + sizeNode.isMinimumSize = false; + } + + // Function Description: + // - Attempts to load some XAML resources that the Pane will need. This includes: + // * The Color we'll use for active Panes's borders - SystemAccentColor + // * The Brush we'll use for inactive Panes - TabViewBackground (to match the + // color of the titlebar) + void ParentPane::_SetupResources() + { + const auto res = Application::Current().Resources(); + const auto tabViewBackgroundKey = winrt::box_value(L"TabViewBackground"); + if (res.HasKey(tabViewBackgroundKey)) + { + winrt::Windows::Foundation::IInspectable obj = res.Lookup(tabViewBackgroundKey); + s_unfocusedBorderBrush = obj.try_as(); + } + else + { + // DON'T use Transparent here - if it's "Transparent", then it won't + // be able to hittest for clicks, and then clicking on the border + // will eat focus. + s_unfocusedBorderBrush = SolidColorBrush{ Colors::Black() }; + } + } + + // Method Description: + // - Builds a tree of LayoutSizeNode that matches the tree of panes. Each node + // has minimum size that the corresponding pane can have. + // Arguments: + // - widthOrHeight: if true operates on width, otherwise on height + // Return Value: + // - Root node of built tree that matches this pane. + ParentPane::LayoutSizeNode ParentPane::_CreateMinSizeTree(const bool widthOrHeight) const + { + const auto size = GetMinSize(); + LayoutSizeNode node(widthOrHeight ? size.Width : size.Height); + if (auto firstChildAsLeaf = _firstChild.try_as()) + { + const auto firstSize = firstChildAsLeaf.GetMinSize(); + node.firstChild = std::make_unique(widthOrHeight ? firstSize.Width : firstSize.Height); + } + else + { + auto firstChildAsParentImpl = winrt::get_self(_firstChild); + node.firstChild = std::make_unique(firstChildAsParentImpl->_CreateMinSizeTree(widthOrHeight)); + } + if (auto secondChildAsLeaf = _secondChild.try_as()) + { + const auto secondSize = secondChildAsLeaf.GetMinSize(); + node.secondChild = std::make_unique(widthOrHeight ? secondSize.Width : secondSize.Height); + } + else + { + auto secondChildAsParentImpl = winrt::get_self(_secondChild); + node.secondChild = std::make_unique(secondChildAsParentImpl->_CreateMinSizeTree(widthOrHeight)); + } + + return node; + } + + // Method Description: + // - Adjusts split position so that no child pane is smaller then its + // minimum size + // Arguments: + // - widthOrHeight: if true, operates on width, otherwise on height. + // - requestedValue: split position value to be clamped + // - totalSize: size (width or height) of the parent pane + // Return Value: + // - split position (value in range <0.0, 1.0>) + float ParentPane::_ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const + { + const auto firstMinSize = _firstChild.GetMinSize(); + const auto secondMinSize = _secondChild.GetMinSize(); + + const auto firstMinDimension = widthOrHeight ? firstMinSize.Width : firstMinSize.Height; + const auto secondMinDimension = widthOrHeight ? secondMinSize.Width : secondMinSize.Height; + + const auto minSplitPosition = firstMinDimension / totalSize; + const auto maxSplitPosition = 1.0f - (secondMinDimension / totalSize); + + return std::clamp(requestedValue, minSplitPosition, maxSplitPosition); + } +} diff --git a/src/cascadia/TerminalApp/ParentPane.h b/src/cascadia/TerminalApp/ParentPane.h new file mode 100644 index 00000000000..0eee19e53d8 --- /dev/null +++ b/src/cascadia/TerminalApp/ParentPane.h @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// Module Name: +// - ParentPane.h +// +// Abstract: +// - Panes are an abstraction by which the terminal can display multiple terminal +// instances simultaneously in a single terminal window. While tabs allow for +// a single terminal window to have many terminal sessions running +// simultaneously within a single window, only one tab can be visible at a +// time. Panes, on the other hand, allow a user to have many different +// terminal sessions visible to the user within the context of a single window +// at the same time. This can enable greater productivity from the user, as +// they can see the output of one terminal window while working in another. +// - See doc/cascadia/Panes.md for a detailed description. +// - Panes can be one of 2 types, parent or leaf. A parent pane contains 2 other panes +// (each of which could itself be a parent or could be a leaf). A leaf pane contains +// a terminal control. +// +// Author(s): +// - Mike Griese (zadjii-msft) 16-May-2019 +// - Pankaj Bhojwani February-2021 + +#pragma once + +#include "inc/cppwinrt_utils.h" +#include "LeafPane.h" + +#include "ParentPane.g.h" + +namespace winrt::TerminalApp::implementation +{ + struct ParentPane : ParentPaneT + { + public: + ParentPane(TerminalApp::LeafPane firstChild, TerminalApp::LeafPane secondChild, winrt::Microsoft::Terminal::Settings::Model::SplitState splitState, float splitPosition); + winrt::Windows::UI::Xaml::Controls::Grid GetRootElement(); + void FocusPane(uint32_t id); + void FocusFirstChild(); + bool HasFocusedChild(); + + bool ContainsReadOnly(); + + void InitializeChildren(); + + void Shutdown(); + void ClearActive(); + + void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, + const GUID& profile); + void ResizeContent(const winrt::Windows::Foundation::Size& newSize); + void Relayout(); + IPane GetActivePane() const; + bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction); + bool NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction); + float CalcSnappedDimensionSingle(const bool widthOrHeight, const float dimension) const; + uint32_t GetLeafPaneCount() const noexcept; + winrt::Windows::Foundation::Size GetMinSize() const; + + void Maximize(IPane paneToZoom); + void Restore(IPane paneToUnzoom); + + winrt::Windows::Foundation::IReference PreCalculateAutoSplit(const IPane target, + const winrt::Windows::Foundation::Size parentSize) const; + winrt::Windows::Foundation::IReference PreCalculateCanSplit(const IPane target, + winrt::Microsoft::Terminal::Settings::Model::SplitState splitType, + const float splitSize, + const winrt::Windows::Foundation::Size availableSpace) const; + + IPane FindFirstLeaf(); + + void PropagateToLeavesOnEdge(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& edge, std::function action); + SnapSizeResult CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; + + TYPED_EVENT(PaneTypeChanged, IPane, IPane); + + private: + struct SnapChildrenSizeResult; + struct LayoutSizeNode; + static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_unfocusedBorderBrush; + + IPane _firstChild; + IPane _secondChild; + winrt::Microsoft::Terminal::Settings::Model::SplitState _splitState{ winrt::Microsoft::Terminal::Settings::Model::SplitState::None }; + float _desiredSplitPosition; + winrt::event_token _firstClosedToken{ 0 }; + winrt::event_token _secondClosedToken{ 0 }; + winrt::event_token _firstTypeChangedToken{ 0 }; + winrt::event_token _secondTypeChangedToken{ 0 }; + + bool _firstLayoutUpdated{ false }; + bool _secondLayoutUpdated{ false }; + winrt::Microsoft::Terminal::Control::TermControl::LayoutUpdated_revoker _firstLayoutRevoker; + winrt::Microsoft::Terminal::Control::TermControl::LayoutUpdated_revoker _secondLayoutRevoker; + + void _CreateRowColDefinitions(); + bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction); + bool _NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction); + + void _ChildrenLayoutUpdatedHelper(const bool isFirstChild); + void _SetupEntranceAnimation(); + void _CloseChild(const bool closeFirst); + winrt::fire_and_forget _CloseChildRoutine(const bool closeFirst); + + void _SetupChildEventHandlers(const bool isFirstChild); + void _RemoveAllChildEventHandlers(const bool isFirstChild); + + void _OnChildSplitOrCollapse(const bool isFirstChild, IPane newChild); + + void _GridLayoutHelper() const noexcept; + + std::pair _CalcChildrenSizes(const float fullSize) const; + SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const; + LayoutSizeNode _CreateMinSizeTree(const bool widthOrHeight) const; + float _ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const; + void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const; + + void _SetupResources(); + + struct SnapChildrenSizeResult + { + std::pair lower; + std::pair higher; + }; + + // Helper structure that builds a (roughly) binary tree corresponding + // to the pane tree. Used for laying out panes with snapped sizes. + struct LayoutSizeNode + { + float size; + bool isMinimumSize; + std::unique_ptr firstChild; + std::unique_ptr secondChild; + + // These two fields hold next possible snapped values of firstChild and + // secondChild. Although that could be calculated from these fields themselves, + // it would be wasteful as we have to know these values more often than for + // simple increment. Hence we cache that here. + std::unique_ptr nextFirstChild; + std::unique_ptr nextSecondChild; + + explicit LayoutSizeNode(const float minSize); + LayoutSizeNode(const LayoutSizeNode& other); + + LayoutSizeNode& operator=(const LayoutSizeNode& other); + + private: + void _AssignChildNode(std::unique_ptr& nodeField, const LayoutSizeNode* const newNode); + }; + + // Function Description: + // - Returns true if the given direction can be used with the given split + // type. + // - This is used for pane resizing (which will need a pane separator + // that's perpendicular to the direction to be able to move the separator + // in that direction). + // - Also used for moving focus between panes, which again happens _across_ a separator. + // Arguments: + // - direction: The Direction to compare + // - splitType: The winrt::TerminalApp::SplitState to compare + // Return Value: + // - true iff the direction is perpendicular to the splitType. False for + // winrt::TerminalApp::SplitState::None. + template + static constexpr bool DirectionMatchesSplit(const T& direction, + const winrt::Microsoft::Terminal::Settings::Model::SplitState& splitType) + { + if (splitType == winrt::Microsoft::Terminal::Settings::Model::SplitState::None) + { + return false; + } + else if (splitType == winrt::Microsoft::Terminal::Settings::Model::SplitState::Horizontal) + { + return direction == T::Up || + direction == T::Down; + } + else if (splitType == winrt::Microsoft::Terminal::Settings::Model::SplitState::Vertical) + { + return direction == T::Left || + direction == T::Right; + } + return false; + } + }; +} + +namespace winrt::TerminalApp::factory_implementation +{ + BASIC_FACTORY(ParentPane); +} diff --git a/src/cascadia/TerminalApp/ParentPane.idl b/src/cascadia/TerminalApp/ParentPane.idl new file mode 100644 index 00000000000..5992483daca --- /dev/null +++ b/src/cascadia/TerminalApp/ParentPane.idl @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "IPane.idl"; +import "LeafPane.idl"; + +namespace TerminalApp +{ + [default_interface] runtimeclass ParentPane : Windows.UI.Xaml.Controls.UserControl, IPane + { + ParentPane(LeafPane firstChild, LeafPane secondChild, Microsoft.Terminal.Settings.Model.SplitState splitState, Single splitPosition); + void InitializeChildren(); + Boolean ResizePane(Microsoft.Terminal.Settings.Model.ResizeDirection direction); + Boolean NavigateFocus(Microsoft.Terminal.Settings.Model.FocusDirection direction); + void Relayout(); + } +} diff --git a/src/cascadia/TerminalApp/ParentPane.xaml b/src/cascadia/TerminalApp/ParentPane.xaml new file mode 100644 index 00000000000..52f1d039bd2 --- /dev/null +++ b/src/cascadia/TerminalApp/ParentPane.xaml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 699c3f5bff8..845879c4749 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -638,11 +638,9 @@ namespace winrt::TerminalApp::implementation { _UnZoomIfNeeded(); - auto pane = terminalTab->GetActivePane(); - if (const auto pane{ terminalTab->GetActivePane() }) { - if (const auto control{ pane->GetTerminalControl() }) + if (const auto control{ pane.TerminalControl() }) { if (control.ReadOnly()) { @@ -661,7 +659,7 @@ namespace winrt::TerminalApp::implementation } } - pane->Close(); + pane.Close(); } } } diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index 2c763b2d446..2400b4d84e1 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -65,6 +65,12 @@ Designer + + Designer + + + Designer + @@ -123,7 +129,12 @@ EmptyStringVisibilityConverter.idl - + + LeafPane.xaml + + + ParentPane.xaml + @@ -206,7 +217,12 @@ EmptyStringVisibilityConverter.idl - + + LeafPane.xaml + + + ParentPane.xaml + @@ -250,6 +266,7 @@ + MinMaxCloseControl.xaml Code @@ -289,6 +306,14 @@ CommandPalette.xaml Code + + LeafPane.xaml + Code + + + ParentPane.xaml + Code + diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters index 1511e14d9c1..e47b3725a36 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters @@ -10,7 +10,10 @@ - + + pane + + pane @@ -60,6 +63,12 @@ pane + + pane + + + pane + tab @@ -116,6 +125,15 @@ tab + + pane + + + pane + + + pane + commandPalette @@ -139,6 +157,12 @@ controls + + pane + + + pane + controls diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 18afb5962a2..126debb3349 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1002,7 +1002,7 @@ namespace winrt::TerminalApp::implementation term.RestorePointerCursor({ get_weak(), &TerminalPage::_RestorePointerCursorHandler }); // Bind Tab events to the TermControl and the Tab's Pane - hostingTab.Initialize(term); + hostingTab.Initialize(); auto weakTab{ hostingTab.get_weak() }; auto weakThis{ get_weak() }; diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index 4a09839407a..3fbb59b769f 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -25,20 +25,17 @@ namespace winrt namespace winrt::TerminalApp::implementation { - TerminalTab::TerminalTab(const GUID& profile, const TermControl& control) + TerminalTab::TerminalTab(const GUID& profile, const TermControl& control) : + _zoomedPane(nullptr) { - _rootPane = std::make_shared(profile, control, true); + _rootPane = make(profile, control, true); + const auto rootPaneAsLeaf = _rootPane.try_as(); - _rootPane->Id(_nextPaneId); + rootPaneAsLeaf.Id(_nextPaneId); _mruPanes.insert(_mruPanes.begin(), _nextPaneId); ++_nextPaneId; - _rootPane->Closed([=](auto&& /*s*/, auto&& /*e*/) { - _ClosedHandlers(nullptr, nullptr); - }); - - _activePane = _rootPane; - Content(_rootPane->GetRootElement()); + Content(rootPaneAsLeaf); _MakeTabViewItem(); _CreateContextMenu(); @@ -144,7 +141,7 @@ namespace winrt::TerminalApp::implementation // that was last focused. TermControl TerminalTab::GetActiveTerminalControl() const { - return _activePane->GetTerminalControl(); + return _rootPane.GetActivePane().try_as().TerminalControl(); } // Method Description: @@ -154,9 +151,9 @@ namespace winrt::TerminalApp::implementation // - control: reference to the TermControl object to bind event to // Return Value: // - - void TerminalTab::Initialize(const TermControl& control) + void TerminalTab::Initialize() { - _BindEventHandlers(control); + _BindEventHandlers(); } // Method Description: @@ -201,7 +198,7 @@ namespace winrt::TerminalApp::implementation // focused, else the GUID of the profile of the last control to be focused std::optional TerminalTab::GetFocusedProfile() const noexcept { - return _activePane->GetFocusedProfile(); + return _rootPane.GetActivePane().try_as().Profile(); } // Method Description: @@ -211,10 +208,13 @@ namespace winrt::TerminalApp::implementation // - control: reference to the TermControl object to bind event to // Return Value: // - - void TerminalTab::_BindEventHandlers(const TermControl& control) noexcept + void TerminalTab::_BindEventHandlers() noexcept { - _AttachEventHandlersToPane(_rootPane); - _AttachEventHandlersToControl(control); + // This function gets called even when SplitPane is invoked (so potentially much after the initial construction + // of the tab), so just in case remove the root pane event handlers before attaching them again so we don't have the + // tab reacting to the same event twice + _RemoveRootPaneEventHandlers(); + _SetupRootPaneEventHandlers(); } // Method Description: @@ -226,7 +226,7 @@ namespace winrt::TerminalApp::implementation // - void TerminalTab::UpdateSettings(const TerminalSettingsCreateResult& settings, const GUID& profile) { - _rootPane->UpdateSettings(settings, profile); + _rootPane.UpdateSettings(settings, profile); // The tabWidthMode may have changed, update the header control accordingly _UpdateHeaderControlMaxWidth(); @@ -409,42 +409,26 @@ namespace winrt::TerminalApp::implementation const GUID& profile, TermControl& control) { - // Make sure to take the ID before calling Split() - Split() will clear out the active pane's ID - const auto activePaneId = _activePane->Id(); - auto [first, second] = _activePane->Split(splitType, splitSize, profile, control); - if (activePaneId) - { - first->Id(activePaneId.value()); - second->Id(_nextPaneId); - ++_nextPaneId; - } - else - { - first->Id(_nextPaneId); - ++_nextPaneId; - second->Id(_nextPaneId); - ++_nextPaneId; - } - _activePane = first; - _AttachEventHandlersToControl(control); + const auto newLeafPane = _rootPane.GetActivePane().try_as().Split(splitType, splitSize, profile, control); + newLeafPane.Id(_nextPaneId); + ++_nextPaneId; - // Add a event handlers to the new panes' GotFocus event. When the pane - // gains focus, we'll mark it as the new active pane. - _AttachEventHandlersToPane(first); - _AttachEventHandlersToPane(second); + // Add the event handlers we need + _AttachEventHandlersToControl(control); + _AttachEventHandlersToLeafPane(newLeafPane); // Immediately update our tracker of the focused pane now. If we're // splitting panes during startup (from a commandline), then it's // possible that the focus events won't propagate immediately. Updating // the focus here will give the same effect though. - _UpdateActivePane(second); + _UpdateActivePane(newLeafPane); } // Method Description: // - See Pane::CalcSnappedDimension float TerminalTab::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const { - return _rootPane->CalcSnappedDimension(widthOrHeight, dimension); + return _rootPane.CalcSnappedDimensionSingle(widthOrHeight, dimension); } // Method Description: @@ -458,7 +442,7 @@ namespace winrt::TerminalApp::implementation { // NOTE: This _must_ be called on the root pane, so that it can propagate // throughout the entire tree. - _rootPane->ResizeContent(newSize); + _rootPane.ResizeContent(newSize); } // Method Description: @@ -472,7 +456,10 @@ namespace winrt::TerminalApp::implementation { // NOTE: This _must_ be called on the root pane, so that it can propagate // throughout the entire tree. - _rootPane->ResizePane(direction); + if (const auto rootPaneAsParent = _rootPane.try_as()) + { + rootPaneAsParent.ResizePane(direction); + } } // Method Description: @@ -487,26 +474,27 @@ namespace winrt::TerminalApp::implementation if (direction == FocusDirection::Previous) { // To get to the previous pane, get the id of the previous pane and focus to that - _rootPane->FocusPane(_mruPanes.at(1)); + _rootPane.FocusPane(_mruPanes.at(1)); } - else + else if (const auto rootPaneAsParent = _rootPane.try_as()) { // NOTE: This _must_ be called on the root pane, so that it can propagate // throughout the entire tree. - _rootPane->NavigateFocus(direction); + rootPaneAsParent.NavigateFocus(direction); } } bool TerminalTab::FocusPane(const uint32_t id) { - return _rootPane->FocusPane(id); + _rootPane.FocusPane(id); + return true; } // Method Description: // - Prepares this tab for being removed from the UI hierarchy by shutting down all active connections. void TerminalTab::Shutdown() { - _rootPane->Shutdown(); + _rootPane.Shutdown(); } // Method Description: @@ -519,7 +507,7 @@ namespace winrt::TerminalApp::implementation // - void TerminalTab::ClosePane() { - _activePane->Close(); + _rootPane.GetActivePane().try_as().Close(); } void TerminalTab::SetTabText(winrt::hstring title) @@ -586,7 +574,10 @@ namespace winrt::TerminalApp::implementation const bool isInitialChange) { if (isInitialChange) { - _rootPane->Relayout(); + if (const auto rootPaneAsParent = _rootPane.try_as()) + { + rootPaneAsParent.Relayout(); + } } }); @@ -686,12 +677,11 @@ namespace winrt::TerminalApp::implementation // - pane: a Pane to mark as active. // Return Value: // - - void TerminalTab::_UpdateActivePane(std::shared_ptr pane) + void TerminalTab::_UpdateActivePane(TerminalApp::LeafPane pane) { // Clear the active state of the entire tree, and mark only the pane as active. - _rootPane->ClearActive(); - _activePane = pane; - _activePane->SetActive(); + _rootPane.ClearActive(); + pane.SetActive(); // Update our own title text to match the newly-active pane. UpdateTitle(); @@ -699,18 +689,16 @@ namespace winrt::TerminalApp::implementation // We need to move the pane to the top of our mru list // If its already somewhere in the list, remove it first - if (const auto paneId = pane->Id()) + const auto paneId = pane.Id(); + for (auto i = _mruPanes.begin(); i != _mruPanes.end(); ++i) { - for (auto i = _mruPanes.begin(); i != _mruPanes.end(); ++i) + if (*i == paneId) { - if (*i == paneId.value()) - { - _mruPanes.erase(i); - break; - } + _mruPanes.erase(i); + break; } - _mruPanes.insert(_mruPanes.begin(), paneId.value()); } + _mruPanes.insert(_mruPanes.begin(), paneId); _RecalculateAndApplyReadOnly(); @@ -723,26 +711,25 @@ namespace winrt::TerminalApp::implementation // focus, we'll mark it as the new active pane. We'll also query the title of // that pane when it's focused to set our own text, and finally, we'll trigger // our own ActivePaneChanged event. + // - Also add an event handler for the pane's raise visual bell event // Arguments: // - // Return Value: // - - void TerminalTab::_AttachEventHandlersToPane(std::shared_ptr pane) + void TerminalTab::_AttachEventHandlersToLeafPane(TerminalApp::LeafPane pane) { auto weakThis{ get_weak() }; - std::weak_ptr weakPane{ pane }; - pane->GotFocus([weakThis](std::shared_ptr sender) { + const auto paneImpl = winrt::get_self(pane); + paneImpl->GotFocus([weakThis](TerminalApp::LeafPane sender) { // Do nothing if the Tab's lifetime is expired or pane isn't new. auto tab{ weakThis.get() }; if (tab) { - if (sender != tab->_activePane) - { - tab->_UpdateActivePane(sender); - tab->_RecalculateAndApplyTabColor(); - } + tab->_UpdateActivePane(sender); + tab->_RecalculateAndApplyTabColor(); + tab->_focusState = WUX::FocusState::Programmatic; // This tab has gained focus, remove the bell indicator if it is active if (tab->_tabStatus.BellIndicator()) @@ -752,7 +739,7 @@ namespace winrt::TerminalApp::implementation } }); - pane->LostFocus([weakThis](std::shared_ptr /*sender*/) { + paneImpl->LostFocus([weakThis](TerminalApp::LeafPane /*sender*/) { // Do nothing if the Tab's lifetime is expired or pane isn't new. auto tab{ weakThis.get() }; @@ -763,35 +750,8 @@ namespace winrt::TerminalApp::implementation } }); - // Add a Closed event handler to the Pane. If the pane closes out from - // underneath us, and it's zoomed, we want to be able to make sure to - // update our state accordingly to un-zoom that pane. See GH#7252. - pane->Closed([weakThis, weakPane](auto&& /*s*/, auto && /*e*/) -> winrt::fire_and_forget { - if (auto tab{ weakThis.get() }) - { - if (tab->_zoomedPane) - { - co_await winrt::resume_foreground(tab->Content().Dispatcher()); - - tab->Content(tab->_rootPane->GetRootElement()); - tab->ExitZoom(); - } - if (auto pane = weakPane.lock()) - { - for (auto i = tab->_mruPanes.begin(); i != tab->_mruPanes.end(); ++i) - { - if (*i == pane->Id()) - { - tab->_mruPanes.erase(i); - break; - } - } - } - } - }); - // Add a PaneRaiseBell event handler to the Pane - pane->PaneRaiseBell([weakThis](auto&& /*s*/, auto&& visual) { + paneImpl->PaneRaiseBell([weakThis](auto&& /*s*/, auto&& visual) { if (auto tab{ weakThis.get() }) { if (visual) @@ -813,6 +773,73 @@ namespace winrt::TerminalApp::implementation } } }); + + paneImpl->Closed([weakThis](TerminalApp::IPane sender, auto&& /*a*/) { + if (auto tab{ weakThis.get() }) + { + if (const auto senderAsLeaf = sender.try_as()) + { + // Update our mru list + const auto senderId = senderAsLeaf.Id(); + for (auto i = tab->_mruPanes.begin(); i != tab->_mruPanes.end(); ++i) + { + if (*i == senderId) + { + tab->_mruPanes.erase(i); + break; + } + } + + // If that was the last leaf, close the tab + if (tab->_mruPanes.empty()) + { + tab->_RemoveRootPaneEventHandlers(); + tab->_ClosedHandlers(nullptr, nullptr); + } + else if (!senderAsLeaf.WasLastFocused()) + { + // If the closed child was not focused, then there is a + // good chance that the last focused pane no longer has focus + // due to the changes made to the UI tree from the close event + // So, focus the most recently used pane just in case + tab->_rootPane.FocusPane(tab->_mruPanes.at(0)); + } + } + } + }); + } + + void TerminalTab::_SetupRootPaneEventHandlers() + { + if (const auto rootPaneAsLeaf = _rootPane.try_as()) + { + // Root pane also belongs to the pane tree, so attach the usual events, as for + // every other pane. + _AttachEventHandlersToLeafPane(rootPaneAsLeaf); + _AttachEventHandlersToControl(rootPaneAsLeaf.TerminalControl()); + } + + // When the root pane is a leaf and gets split, it produces a new parent pane that contains + // both itself and its new leaf neighbor. We then replace the root pane with the new parent pane. + + // When root pane is a parent and one of its children got closed (and so the parent collapses), + // we take in its remaining, orphaned child as our own. + + // Either way, the event handling is the same - update the event handlers and set the tab's content to the new pane + _rootPaneTypeChangedToken = _rootPane.PaneTypeChanged([weakThis = get_weak()](auto&& /*s*/, TerminalApp::IPane newPane) { + if (auto tab{ weakThis.get() }) + { + tab->_RemoveRootPaneEventHandlers(); + tab->_rootPane = newPane; + tab->_SetupRootPaneEventHandlers(); + tab->Content(newPane.try_as()); + } + }); + } + + void TerminalTab::_RemoveRootPaneEventHandlers() + { + _rootPane.PaneTypeChanged(_rootPaneTypeChangedToken); } // Method Description: @@ -1142,7 +1169,7 @@ namespace winrt::TerminalApp::implementation // - The total number of leaf panes hosted by this tab. int TerminalTab::GetLeafPaneCount() const noexcept { - return _rootPane->GetLeafPaneCount(); + return _rootPane.GetLeafPaneCount(); } // Method Description: @@ -1157,14 +1184,23 @@ namespace winrt::TerminalApp::implementation // `availableSpace` SplitState TerminalTab::PreCalculateAutoSplit(winrt::Windows::Foundation::Size availableSpace) const { - return _rootPane->PreCalculateAutoSplit(_activePane, availableSpace).value_or(SplitState::Vertical); + const auto res = _rootPane.PreCalculateAutoSplit(_rootPane.GetActivePane(), availableSpace); + return res ? res.Value() : SplitState::Vertical; } bool TerminalTab::PreCalculateCanSplit(SplitState splitType, const float splitSize, winrt::Windows::Foundation::Size availableSpace) const { - return _rootPane->PreCalculateCanSplit(_activePane, splitType, splitSize, availableSpace).value_or(false); + const auto res = _rootPane.PreCalculateCanSplit(_rootPane.GetActivePane(), splitType, splitSize, availableSpace); + if (res) + { + return res.Value(); + } + else + { + return false; + } } // Method Description: @@ -1190,19 +1226,27 @@ namespace winrt::TerminalApp::implementation } void TerminalTab::EnterZoom() { - _zoomedPane = _activePane; - _rootPane->Maximize(_zoomedPane); + _zoomedPane = _rootPane.GetActivePane().try_as(); + _rootPane.Maximize(_zoomedPane); + // Update the tab header to show the magnifying glass _tabStatus.IsPaneZoomed(true); - Content(_zoomedPane->GetRootElement()); + Content(_zoomedPane); } void TerminalTab::ExitZoom() { - _rootPane->Restore(_zoomedPane); + _rootPane.Restore(_zoomedPane); _zoomedPane = nullptr; // Update the tab header to hide the magnifying glass _tabStatus.IsPaneZoomed(false); - Content(_rootPane->GetRootElement()); + if (const auto rootAsLeaf = _rootPane.try_as()) + { + Content(rootAsLeaf); + } + else + { + Content(_rootPane.try_as()); + } } bool TerminalTab::IsZoomed() @@ -1234,13 +1278,13 @@ namespace winrt::TerminalApp::implementation _tabStatus.IsReadOnlyActive(isReadOnlyActive); } - ReadOnly(_rootPane->ContainsReadOnly()); + ReadOnly(_rootPane.ContainsReadOnly()); TabViewItem().IsClosable(!ReadOnly()); } - std::shared_ptr TerminalTab::GetActivePane() const + TerminalApp::LeafPane TerminalTab::GetActivePane() const { - return _activePane; + return _rootPane.GetActivePane().try_as(); } // Method Description: diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h index 7167f74a67c..dc4dedf3b0d 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -2,9 +2,10 @@ // Licensed under the MIT license. #pragma once -#include "Pane.h" #include "ColorPickupFlyout.h" #include "TabBase.h" +#include "LeafPane.h" +#include "ParentPane.h" #include "TerminalTab.g.h" static constexpr double HeaderRenameBoxWidthDefault{ 165 }; @@ -24,7 +25,7 @@ namespace winrt::TerminalApp::implementation TerminalTab(const GUID& profile, const winrt::Microsoft::Terminal::Control::TermControl& control); // Called after construction to perform the necessary setup, which relies on weak_ptr - void Initialize(const winrt::Microsoft::Terminal::Control::TermControl& control); + void Initialize(); winrt::Microsoft::Terminal::Control::TermControl GetActiveTerminalControl() const; std::optional GetFocusedProfile() const noexcept; @@ -80,7 +81,7 @@ namespace winrt::TerminalApp::implementation int GetLeafPaneCount() const noexcept; void TogglePaneReadOnly(); - std::shared_ptr GetActivePane() const; + TerminalApp::LeafPane GetActivePane() const; winrt::TerminalApp::TerminalTabStatus TabStatus() { @@ -95,9 +96,8 @@ namespace winrt::TerminalApp::implementation TYPED_EVENT(TaskbarProgressChanged, IInspectable, IInspectable); private: - std::shared_ptr _rootPane{ nullptr }; - std::shared_ptr _activePane{ nullptr }; - std::shared_ptr _zoomedPane{ nullptr }; + IPane _rootPane; + TerminalApp::LeafPane _zoomedPane; winrt::hstring _lastIconPath{}; winrt::TerminalApp::ColorPickupFlyout _tabColorPickup{}; std::optional _themeTabColor{}; @@ -105,6 +105,8 @@ namespace winrt::TerminalApp::implementation winrt::TerminalApp::TabHeaderControl _headerControl{}; winrt::TerminalApp::TerminalTabStatus _tabStatus{}; + winrt::event_token _rootPaneTypeChangedToken{ 0 }; + std::vector _mruPanes; uint32_t _nextPaneId{ 0 }; @@ -129,12 +131,14 @@ namespace winrt::TerminalApp::implementation void _RefreshVisualState(); - void _BindEventHandlers(const winrt::Microsoft::Terminal::Control::TermControl& control) noexcept; + void _BindEventHandlers() noexcept; void _AttachEventHandlersToControl(const winrt::Microsoft::Terminal::Control::TermControl& control); - void _AttachEventHandlersToPane(std::shared_ptr pane); + void _AttachEventHandlersToLeafPane(TerminalApp::LeafPane pane); + void _SetupRootPaneEventHandlers(); + void _RemoveRootPaneEventHandlers(); - void _UpdateActivePane(std::shared_ptr pane); + void _UpdateActivePane(TerminalApp::LeafPane pane); winrt::hstring _GetActiveTitle() const;