Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changing fallback behaviour in PlatformKeyGestureConverter.ToPlatformString() to be more user friendly. #15393

Closed

Conversation

IanRawley
Copy link
Contributor

@IanRawley IanRawley commented Apr 16, 2024

PlatformKeyGestureConverter.ToPlatformString() falls back to KeyGesture.ToString() rather than it's own user friendly string creation method ToString(KeyGesture gesture, string meta). This will print Key enum values such as "OemPeriod" or "D1" rather than more userfriendly strings like "." or "1"

What does the pull request do?

Change the fallback case to use ToString(gesture, "Cmd") rather than gesture.ToString(). This will print user friendly key values for when platform specific cases aren't implemented. Specifically Android.

What is the current behavior?

Key enum strings are printed.

What is the updated/expected behavior with this PR?

More user friendly values are printed.

How was the solution implemented (if it's not obvious)?

Checklist

Breaking changes

None I can think of.

Fixed issues

Fixes #15392

Other Notes:
I considered adding an Android specific path as well, but couldn't come to a decision about what the appropriate "Meta" string would be. The 2 Android Devices I checked use 🔍 (U+1F50D) for the OS displayed shortcuts list, but in practice it depends on the actual physical keyboard. The Logitech K480 I have for testing triggers those shortcuts with a key labeled "start | alt opt", while the actual key with a magnifying glass on it does not trigger them. Another option I found was using "OS" for the meta label, but that seems to be ROM specific. I'm not sure there is a good universal "Meta" string, so left it at as the fallback case with "Cmd", which is a minimal change from current behaviour.

… ToString(gesture, meta) method rather than gesture.ToString(). This will ensure Keys where the enum value doesn't match the KeySymbol are printed in a more readily understood format for users on platforms that don't have a specific implementation, e.g. Android.
@avaloniaui-bot
Copy link

You can test this PR using the following package version. 11.2.999-cibuild0047319-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@@ -57,7 +57,7 @@ public static string ToPlatformString(KeyGesture gesture)
}
else
{
return gesture.ToString();
return ToString(gesture, "Cmd");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Win" key would make more sense since it's likely an external keyboard we are dealing with.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with Cmd, because that's what the gesture.ToString() prints for Meta. I agree it's almost certainly an external keyboard, they don't all say Win. The logitech K480 I've been using for testing is "Start | alt opt".

Copy link
Contributor Author

@IanRawley IanRawley Apr 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing to keep in mind is that this isn't Android specific, it's any runtime that's not nominally Windows, Linux, or OSX. Edit: Just confirmed this code path is also hit by Browser apps.

Actually looking deeper, it seems using RuntimeInformation.IsPlatform() might no longer be the appropriate way to do this, as a lot of the tests have been added to System.OperatingSystem with more granularity (Browser, Android, iOS, etc).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with Cmd, because that's what the gesture.ToString() prints for Meta

That's a good point: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Base/Input/KeyGesture.cs#L130
Possibly it's worth reusing code between PlatformKeyGestureConverter and KeyGesture. Their ToString() and Parse() combination should be compatible and unit tested.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually looking deeper, it seems using RuntimeInformation.IsPlatform() might no longer be the appropriate way to do this

In general, yes. We should use OperatingSystem API (or OperatingSystemEx in Avalonia repo). But you probably won't get much out of it, unless you need to add cases for mobile and browser as well. It also won't tell you on which platform browser is running.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the most part the ToString functions are the same. The biggest difference is the Meta variations in the converter, and using a variety of symbols for the OSXString. These could probably be moved to KeyGesture.ToString() in some way, and then the converter simply checks the platform and calls as appropriate. Would it be desirable for the default KeyGesture.ToString() to print user friendly characters rather than enum strings, or would that be considered a breaking change? I suspect it would.

From what I understand of Key parsing, the default ToString should probably use the Enum value strings, or digits will cause problems.
So maybe something like?:

KeyGesture.ToString(string metaOverride = "cmd", bool prettyPrint = false)
KeyGesture.ToString() => ToString("Cmd", false)
KeyGesture.ToOSXString()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a second look at this issue. You are right, OSX differences in formatted strings make it more difficult.

For the most part, our differences between platform hotkeys are defined by the PlatformHotkeyConfiguration class.
Which also can be redefined by the platform OS as well. Which allows us to avoid RuntimeInformation.IsOSPlatform checks in shared code.

What do you think about:

  1. KeyGesture.ToString() to always use enum values.
  2. Add KeyDisplayName properties to PlatformHotkeyConfiguration or something like this. Can be a dictionary, I guess.
  3. KeyGesture.ToFormattedString() that internally uses PlatformHotkeyConfiguration configuration for special key display names. No need for ToOSXString.
  4. KeyGesture.Parse/TryParse expects enum values, as it's normally defined in XAML as well. Need to make sure that XAML compiler also uses that.
  5. PlatformKeyGestureConverter to use KeyGesture.ToFormattedString internally.

Now, ToFormattedString is not as common in .NET BCL. There are methods named like this in .NET in general, but I couldn't find any in BCL itself. Normally CultureInfo parameter makes all difference for ToString formatting strategy of different types, but we can't use it here. https://www.google.com/search?q=ToFormattedString%20site:learn.microsoft.com

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to follow the BCL, I think we would need to implement IFormattable/ISpanFormattable in KeyGesture. Then we can use the format string + the format provider to output the gesture as desired.

A new type would provide the modifier key strings (or PlatformHotkeyConfiguration as you mentioned, but I think that isn't its responsability) and implement IFormatProvider.

Then KeyGesture can look for the needed format in IFormatProvider, falling back to the current platform and/or invariant values as needed. People are able to provide the format they need.

Sample, using n as the invariant format and p as platform specific ones:

public sealed class KeyModifierFormatInfo(string meta) : IFormatProvider
{
	public string Meta { get; } = meta;
        // TODO: Ctrl, Shift, etc.

	public object? GetFormat(Type? formatType)
		=> formatType == typeof(KeyModifierFormatInfo) ? this : null;

	public static KeyModifierFormatInfo Invariant { get; } = new("Meta");

	public static KeyModifierFormatInfo GetInstance(IFormatProvider? formatProvider)
		=> formatProvider?.GetFormat(typeof(KeyModifierFormatInfo)) as KeyModifierFormatInfo
		   ?? AvaloniaLocator.Current.GetService<KeyModifierFormatInfo>()
		   ?? Invariant;
}

// In KeyGesture:
public override string ToString()
	=> ToString(null, null);

public string ToString(string? format, IFormatProvider? formatProvider)
{
	var formatInfo = format switch
	{
		null or "" or "n" => KeyModifierFormatInfo.Invariant,
		"p" => KeyModifierFormatInfo.GetInstance(formatProvider),
		_ => throw new FormatException("Unknown format specifier");
	};

	// output gesture using formatInfo here
	// ...
}

Then it's the matter of documenting the available formats, and its link to KeyModifierFormatInfo in the rare event where an user wants to completely customize the output. it might feel a bit over-engineered, but that's the pattern used everywhere by the BCL.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, I have a bit of time to work on this now. Where would the KeyModifierFormatInfo class (or equivalent) go in the Avalonia projects? Avalonia.Controls or Avalonia.Base are where I'm digging around at the moment, but if there's a better spot please let me know.

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 11.2.999-cibuild0047455-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@IanRawley
Copy link
Contributor Author

I've done the implementation of IFormattable and opened a new pull request, #15828 using the guidelines provided by MrJul. Please check that pull request and let me know what changes still need to be made.

@IanRawley IanRawley closed this May 27, 2024
@cla-avalonia
Copy link
Collaborator

@IanRawley,

Please read the following Contributor License Agreement (CLA). If you agree with the CLA, please reply with the following:

@cla-avalonia agree
Contributor License Agreement

Contribution License Agreement

This Contribution License Agreement ( “Agreement” ) is agreed to by the party signing below ( “You” ),
and conveys certain license rights to the AvaloniaUI OÜ ( “AvaloniaUI OÜ” ) for Your contributions to
AvaloniaUI OÜ open source projects. This Agreement is effective as of the latest signature date below.

1. Definitions.

“Code” means the computer software code, whether in human-readable or machine-executable form,
that is delivered by You to AvaloniaUI OÜ under this Agreement.

“Project” means any of the projects owned or managed by AvaloniaUI OÜ and offered under a license
approved by the Open Source Initiative (www.opensource.org).

“Submit” is the act of uploading, submitting, transmitting, or distributing code or other content to any
Project, including but not limited to communication on electronic mailing lists, source code control
systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of
discussing and improving that Project, but excluding communication that is conspicuously marked or
otherwise designated in writing by You as “Not a Submission.”

“Submission” means the Code and any other copyrightable material Submitted by You, including any
associated comments and documentation.

2. Your Submission. You must agree to the terms of this Agreement before making a Submission to any
Project. This Agreement covers any and all Submissions that You, now or in the future (except as
described in Section 4 below), Submit to any Project.

3. Originality of Work. You represent that each of Your Submissions is entirely Your
original work. Should You wish to Submit materials that are not Your original work,
You may Submit them separately to the Project if You (a) retain all copyright and
license information that was in the materials as you received them, (b) in the
description accompanying your Submission, include the phrase "Submission
containing materials of a third party:" followed by the names of the third party and any
licenses or other restrictions of which You are aware, and (c) follow any other
instructions in the Project's written guidelines concerning Submissions.

4. Your Employer. References to “employer” in this Agreement include Your employer or anyone else
for whom You are acting in making Your Submission, e.g. as a contractor, vendor, or agent. If Your
Submission is made in the course of Your work for an employer or Your employer has intellectual
property rights in Your Submission by contract or applicable law, You must secure permission from Your
employer to make the Submission before signing this Agreement. In that case, the term “You” in this
Agreement will refer to You and the employer collectively. If You change employers in the future and
desire to Submit additional Submissions for the new employer, then You agree to sign a new Agreement
and secure permission from the new employer before Submitting those Submissions.

5. Licenses.

a. Copyright License. You grant AvaloniaUI OÜ, and those who receive the Submission directly
or indirectly from AvaloniaUI OÜ, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable
license in the Submission to reproduce, prepare derivative works of, publicly display, publicly perform,
and distribute the Submission and such derivative works, and to sublicense any or all of the foregoing
rights to third parties.

b. Patent License. You grant AvaloniaUI OÜ, and those who receive the Submission directly or
indirectly from AvaloniaUI OÜ, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license
under Your patent claims that are necessarily infringed by the Submission or the combination of the
Submission with the Project to which it was Submitted to make, have made, use, offer to sell, sell and
import or otherwise dispose of the Submission alone or with the Project.

c. Other Rights Reserved. Each party reserves all rights not expressly granted in this Agreement.
No additional licenses or rights whatsoever (including, without limitation, any implied licenses) are
granted by implication, exhaustion, estoppel or otherwise.

6. Representations and Warranties. You represent that You are legally entitled to grant the above
licenses. You represent that each of Your Submissions is entirely Your original work (except as You may
have disclosed under Section 3 ). You represent that You have secured permission from Your employer to
make the Submission in cases where Your Submission is made in the course of Your work for Your
employer or Your employer has intellectual property rights in Your Submission by contract or applicable
law. If You are signing this Agreement on behalf of Your employer, You represent and warrant that You
have the necessary authority to bind the listed employer to the obligations contained in this Agreement.
You are not expected to provide support for Your Submission, unless You choose to do so. UNLESS
REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, AND EXCEPT FOR THE WARRANTIES
EXPRESSLY STATED IN SECTIONS 3, 4, AND 6 , THE SUBMISSION PROVIDED UNDER THIS AGREEMENT IS
PROVIDED WITHOUT WARRANTY OF ANY KIND, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY OF
NONINFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.

7. Notice to AvaloniaUI OÜ. You agree to notify AvaloniaUI OÜ in writing of any facts or
circumstances of which You later become aware that would make Your representations in this
Agreement inaccurate in any respect.

8. Information about Submissions. You agree that contributions to Projects and information about
contributions may be maintained indefinitely and disclosed publicly, including Your name and other
information that You submit with Your Submission.

9. Governing Law/Jurisdiction. This Agreement is governed by the laws of the Republic of Estonia, and
the parties consent to exclusive jurisdiction and venue in the courts sitting in Talinn,
Estonia. The parties waive all defenses of lack of personal jurisdiction and forum non-conveniens.

10. Entire Agreement/Assignment. This Agreement is the entire agreement between the parties, and
supersedes any and all prior agreements, understandings or communications, written or oral, between
the parties relating to the subject matter hereof. This Agreement may be assigned by AvaloniaUI OÜ.

AvaloniaUI OÜ dedicates this Contribution License Agreement to the public domain according to the Creative Commons CC0 1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Android Menus display InputGesture in a non-friendly way.
6 participants