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

Alt Key accelerators aren't rendered #196

Closed
tomlm opened this issue Dec 8, 2024 · 16 comments · Fixed by #205, #206 or #207
Closed

Alt Key accelerators aren't rendered #196

tomlm opened this issue Dec 8, 2024 · 16 comments · Fixed by #205, #206 or #207
Assignees
Labels
bug Something isn't working

Comments

@tomlm
Copy link
Collaborator

tomlm commented Dec 8, 2024

Menu, Button, etc.

I looked into it and figured this much out:

  • There is a AccessKeyHandler which listens at the visual root level.
  • Whenever a control is part of the parent it registers (somehow) with it to be notified when an AccessKey should be displayed.
  • The AccessKeyHandler has an owner set to it, and when it has an owner it registers for key events and toggles the ShowAccessKeys property, which triggers all of the children to render with the _ on the character.
  • Each element looks for the AccessKeyHandler (somehow) and subscribes to changes on the ShowAccessKeys property.
  • ConsoleWindow was not mapping Alt key correctly, it was coming through as a space.

I updated ConsoleWindow.cs to look like this:

 private void HandleKeyInput(WindowsConsole.InputRecord inputRecord)
 {
     WindowsConsole.KeyEventRecord keyEvent = inputRecord.KeyEvent;
     RawInputModifiers modifiers =
         ModifiersFlagTranslator.Translate(keyEvent.dwControlKeyState);
     char character = keyEvent.UnicodeChar;
     Avalonia.Input.Key key;
     if (character == '\0')
     {
        // modifier only key, there is no character, avalonia maps this as a Key.LeftAlt key etc.
         switch (modifiers)
         {
             case RawInputModifiers.Control:
                 key = Avalonia.Input.Key.LeftCtrl;
                 break;
             case RawInputModifiers.Alt:
                 key = Avalonia.Input.Key.LeftAlt;
                 break;
             case RawInputModifiers.Shift:
                 key = Avalonia.Input.Key.LeftShift;
                 break;
             default:
                 key = Avalonia.Input.Key.None;
                 break;
         }
     }
     else
         key = DefaultNetConsole.ConvertToKey((ConsoleKey)keyEvent.wVirtualKeyCode);

     RaiseKeyPress(key, character, modifiers, keyEvent.bKeyDown, (ulong)Stopwatch.GetTimestamp());
 }

Much to my dismay, this did not fix it. I think that somehow the accesskeyhandler is not being registered correctly because our root window is a ConsoleWindow, but I haven't figured out where/how that happens. It looks like it might have something to do with TopLevel, which we don't even implement? Each control calls RegisterServices and inside there it seems to create an accesshandler...?

Anyhow. I though I would file this bug with my learnings to hopefully help either me or the next guy who comes along and tries to fix this.

@tomlm tomlm added the bug Something isn't working label Dec 8, 2024
@jinek jinek self-assigned this Dec 8, 2024
@jinek
Copy link
Owner

jinek commented Dec 8, 2024

I don't understand what should work. What is AccessKey? I see HotKey on the Button for instance.

@tomlm
Copy link
Collaborator Author

tomlm commented Dec 8, 2024

If you give a button, input control label or menu item a name with _ in front of it you can use ALT+the following letter to activate it.

 <Menu>
   <MenuItem Header="_First">
     <MenuItem Header="Standard _Menu Item" InputGesture="Ctrl+A" />
     <MenuItem Header="_Disabled Menu Item" IsEnabled="False" InputGesture="Ctrl+D" />
     <Separator />
     <MenuItem Header="Menu with Sub _Menu">
       <MenuItem Header="Submenu _1" />
       <MenuItem Header="Submenu _2 with Submenu">
         <MenuItem Header="Submenu Level 2" />
       </MenuItem>

When you press the ALT key, all of the accelerator letters will get an underscore rendered under them.
accelerator

So ALT+F => opens File menu
ALT+F + O => Opens File Menu, selects Open menuItem
etc.

@jinek
Copy link
Owner

jinek commented Dec 12, 2024

Stack trace when access key was pressed on Avalonia under Linux:

MainWindow.Button_OnClick() at /home/jinek/Repo/Trash/AvaloniaApplication1/AvaloniaApplication1/MainWindow.axaml.cs:line 86
Interactive.<AddHandler>g__InvokeAdapter|4_0<Avalonia.Interactivity.RoutedEventArgs>() at /_/src/Avalonia.Base/Interactivity/Interactive.cs:line 67
Interactive.<>c__4<RoutedEventArgs>.<AddHandler>b__4_1()
EventRoute.RaiseEventImpl() [3]
EventRoute.RaiseEvent() [3]
Interactive.RaiseEvent() [3]
Button.OnClick()
Button.OnAccessKey()
Button.<>c.<.cctor>b__15_0()
RoutedEvent<RoutedEventArgs>.<>c__DisplayClass1_0<Button>.<AddClassHandler>g__Adapter|0()
RoutedEvent.<>c__DisplayClass23_0.<AddClassHandler>b__0()
AnonymousObserver<(object, RoutedEventArgs)>.OnNext()
LightweightObservableBase<(object, RoutedEventArgs)>.PublishNext()
LightweightSubject<(object, RoutedEventArgs)>.OnNext()
RoutedEvent.InvokeRaised()
EventRoute.RaiseEventImpl() [2]
EventRoute.RaiseEvent() [2]
Interactive.RaiseEvent() [2]
AccessKeyHandler.OnKeyDown()
[Lightweight Method Call]
MethodInvoker.Invoke()
RuntimeMethodInfo.Invoke()
Delegate.DynamicInvokeImpl()
EventRoute.RaiseEventImpl() [1]
EventRoute.RaiseEvent() [1]
Interactive.RaiseEvent() [1]
KeyboardDevice.ProcessRawEvent()
InputManager.ProcessInput()
TopLevel.HandleInput() at /home/jinek/.config/JetBrains/Rider2024.3/resharper-host/SourcesCache/29f3b7d33f648d907396766a137fb623e567f92649bd8e1d4b1f8ac8e20e247/TopLevel.cs:line 744
X11Window.DispatchInput()
RawEventGrouper.Dispatch()
ManualRawEventGrouperDispatchQueue.DispatchNext()
X11PlatformThreading.RunLoop()
DispatcherFrame.Run()
Dispatcher.PushFrame()
Dispatcher.MainLoop()
ClassicDesktopStyleApplicationLifetime.Start()
ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime()
Program.Main()

@tomlm
Copy link
Collaborator Author

tomlm commented Dec 12, 2024

Yeah I think the buttons works it's the visualization that doesn't

@jinek
Copy link
Owner

jinek commented Dec 12, 2024

Yeah I think the buttons works it's the visualization that doesn't

No, ALT and other modifiers are not processed in Consolonia (but do in Avalonia).
That I will fix for Windows. But it seems with Linux the story is much more complicated. Probably ALT can not be processed or at least can not be in most terminals. One more restriction is ALT can go with combination to only several predefined keys, not all.
For now can not tell regarding visualization. First will fix Windows part of the keys processing..

@jinek
Copy link
Owner

jinek commented Dec 12, 2024

I've done handling for Windows (CTRL, SHIFT and ALT added).

The question:
Access key drawing is done by AccessText.RenderCore:

private protected override void RenderCore(DrawingContext context)
        {
            base.RenderCore(context);
            int underscore = Text?.IndexOf('_') ?? -1;

            if (underscore != -1 && ShowAccessKey)
            {
                var rect = TextLayout!.HitTestTextPosition(underscore);

                var x1 = Math.Round(rect.Left, MidpointRounding.AwayFromZero);
                var x2 = Math.Round(rect.Right, MidpointRounding.AwayFromZero);
                var y = Math.Round(rect.Bottom, MidpointRounding.AwayFromZero) - 1.5;

                context.DrawLine(
                    new Pen(Foreground, 1),
                    new Point(x1, y),
                    new Point(x2, y));
            }
        }

@tomlm We probably can not draw this properly, right? Do we need some own special AccessText control?

@tomlm
Copy link
Collaborator Author

tomlm commented Dec 12, 2024

Oh awesome! Just make the pen stroke thickness UnderlineThickness and it will turn the text to TextDecoration underline!

internal class DrawingContextImpl : IDrawingContextImpl
    {
        public const int UnderlineThickness = 10;

@tomlm
Copy link
Collaborator Author

tomlm commented Dec 12, 2024

oh I see, I think a custom AccessText with UnderlineThickness. Kind of sucks but should work.

Technically it's an avalonia bug, as AccessText should be using FontMetrics.UnderlineThickness which in our case is set to the correct magic value.

@tomlm
Copy link
Collaborator Author

tomlm commented Dec 12, 2024

I filed a bug with Avalonia, AvaloniaUI/Avalonia#17763
I may try to do a PR back to Avalonia...unless you feel inspired to do it.

@jinek
Copy link
Owner

jinek commented Dec 12, 2024

But how does it help us. Its entire string being rendered, will be entire string underline, not just one symbol

@tomlm
Copy link
Collaborator Author

tomlm commented Dec 13, 2024

no, it's using a pen over the top of characters to draw the underline. Avalonia doesn't use the font system to underline, it draws them with pens. It's drawing an underline under just the accelerator key character.

TextBlock normally uses the FontMetric.UnderlineThickness to do that, and so I use a thickness of 10 to signify that we should apply TextDecoration of underline to the pixel.

Our FontMetric.UnderlinePosition = -1, so it causes the line to be drawn on the same pixels that are the text, and the thickness tells me to change the TextDecoration of the text....then when we render we Esc output the text with underline escape sequence.

@jinek jinek linked a pull request Dec 13, 2024 that will close this issue
@jinek jinek removed a link to a pull request Dec 13, 2024
@jinek
Copy link
Owner

jinek commented Dec 13, 2024

As a workaround we can implement own AccessText.

@tomlm
Copy link
Collaborator Author

tomlm commented Dec 13, 2024

Blarg. AccessText is using internal shit.

@jinek jinek linked a pull request Dec 14, 2024 that will close this issue
@jinek
Copy link
Owner

jinek commented Dec 14, 2024

I want to check what we've got on linux and default .net console also

@jinek jinek linked a pull request Dec 14, 2024 that will close this issue
@jinek
Copy link
Owner

jinek commented Dec 14, 2024

@tomlm UX question: in some cases we can not receive ALT key alone.
Should we always draw the underline? Or should we define hotkey to switch on underlines?
Should we give a developer easy option to switch it on off?


My suggesion:
Draw understrike always. But expose a method to switch it off and bind some keys to show them, like:

public static AppBuilder BuildAvaloniaApp()
         => AppBuilder.Configure<App>()
                .UseConsolonia()
                .UseAutoDetectedConsole()
                .NoAccessKeysAlwaysOn(new KeyGesture(Key.A, KeyModifiers.Alt)) // !
                .LogToException();

@jinek jinek linked a pull request Dec 15, 2024 that will close this issue
@tomlm
Copy link
Collaborator Author

tomlm commented Dec 16, 2024

I think leaving them on is fine. That's what Windows 3.x used to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
2 participants