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

Improve ColorPicker picker shape keyboard and joypad accessibility #99374

Open
wants to merge 34 commits into
base: master
Choose a base branch
from

Conversation

FeniXb3
Copy link

@FeniXb3 FeniXb3 commented Nov 17, 2024

While experimenting with accessibility, I realized that ColorPicker does not allow you to move the picker cursor around the color rectangle/wheel with keyboard or joypad (and I needed it, as it's the only part of ColorPicker that I want to show).

This PR implements:

  • ability of both parts of picker shape to grab focus,
  • setting picker focus StyleBox in theme,
  • modifying picker cursor position with keyboard or joypad,
  • keeping focus on proper control while changing picker shape,
  • automatic focusing on the picker shape if it is visible and the HTML LineEdit is not visible (as it's what is currently being focused by default).

How it works in game and in the editor:

ColorPicker.keyaboard.and.joypad.game.mp4
ColorPicker.keyboard.editor.mp4

I don't really like how it works with the controller, as I'm using gui_input to check for actions and the controller does not send echo events, but it's possible to use the controller at least.

@FeniXb3 FeniXb3 requested review from a team as code owners November 17, 2024 22:49
@KoBeWi KoBeWi added this to the 4.x milestone Nov 17, 2024
@AThousandShips AThousandShips changed the title Colorpicker picker shape keyboard and joypad accessibility Improve ColorPicker picker shape keyboard and joypad accessibility Nov 18, 2024
Copy link
Member

@fire fire left a comment

Choose a reason for hiding this comment

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

I support this usability improvement in the proposal design, but it needs technical review.

scene/gui/color_picker.cpp Outdated Show resolved Hide resolved
scene/gui/color_picker.cpp Outdated Show resolved Hide resolved
scene/gui/color_picker.cpp Outdated Show resolved Hide resolved
scene/gui/color_picker.cpp Outdated Show resolved Hide resolved
scene/gui/color_picker.cpp Outdated Show resolved Hide resolved
@FeniXb3
Copy link
Author

FeniXb3 commented Nov 28, 2024

The hue slider should go faster when you adjust it with the keyboard arrows or gamepad buttons. Compare its speed to the color value on the left:

color_value_vs_hue_slider.mp4

Ideally, the speed at which the hue is adjusted should match the color value on the left, which is probably 3× to 4× faster. This can be done by increasing the increment for each button press on the hue slider.

The hue slider goes exactly 3.6× slower than value slider in another shape, so your estimations were pretty correct. ^^ It is by design - value and saturation have values 0-100, but hue has values from range 0-360. Making each button press to increase hue in bigger increments always would miss some values. But I added a multiplier if the event is echo, so after few repetions it speeds up. But again, as for now it does not affect controllers.

What I'm wondering now is if we should have it configurable in the editor, both for games and for ColorPickers in the editor somehow (in editor settings?). What do you think @Calinou ?

When using a color picker with a wheel shape, moving upwards should "slide" along the circle when you've reached the top (same for any other edge):

color_value_wheel_slide.mp4

Right now, it stops so you need to manually move to the left then move back up again.

Fixed, please check it now:

Nagrywanie.ekranu.2024-11-28.163308.mp4

I thought about making the cursor to spin around on the edge if you keep holding the same button, but while it went well for the HSV Wheel shape, it was buggy for both Circle shapes, so I stashed it for now. This is how it looked for HSV Wheel:

Nagrywanie.ekranu.2024-11-28.160945.mp4

I am not sure if it is worth to try implementing it well for all Circle shapes or skip it, as I don't know if anyone will take advantage of this feature.

@FeniXb3 FeniXb3 requested a review from Calinou November 28, 2024 15:43
@Calinou
Copy link
Member

Calinou commented Nov 28, 2024

What I'm wondering now is if we should have it configurable in the editor, both for games and for ColorPickers in the editor somehow (in editor settings?). What do you think @Calinou ?

This would be a pretty niche option, so I'd focus on picking a good default instead.

@akien-mga akien-mga requested a review from KoBeWi December 2, 2024 13:21
@KoBeWi
Copy link
Member

KoBeWi commented Dec 2, 2024

#82979 could be used for better joypad navigation if it was merged. For now the only way is implement the behavior using NOTIFICATION_PROCESS manually, like it was done in Slider for example.

Though the joypad navigation is a bit janky right now. When you enter the main color box, the only way out is either accepting or canceling; you can no longer select another element. There could be 2 focus modes here: one for navigating the elements and one for changing colors using the elements. You could switch them with accept/cancel. We have that in LineEdit.
Though it's something that can be added later.

@KoBeWi
Copy link
Member

KoBeWi commented Dec 3, 2024

The wheel is difficult to use.
image
You need to press the direction depending on the current rotation. It would be easier if you could press a button to start moving and then it would rotate as long as it's still pressed, without needing to press other buttons.

@@ -267,6 +274,8 @@ class ColorPicker : public VBoxContainer {
void _sample_draw();
void _hsv_draw(int p_which, Control *c);
void _slider_draw(int p_which);
int get_wheel_h_change(Vector2 color_change_vector);
Copy link
Member

Choose a reason for hiding this comment

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

Unused.

Comment on lines +2216 to +2217
// HACK: I cannot draw focus stylebox on the wheel itself, as it's drawing based on shader.
wheel_h_focus_display = memnew(Control);
Copy link
Member

Choose a reason for hiding this comment

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

Can't you use wheel_uv for that?

Copy link
Author

Choose a reason for hiding this comment

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

In one moment I did. But it meant that I had to add logic for navigating between two focus modes for this control, capturing next/prev actions, because wheel_uv would be used to handle focus for both parts of HSV Wheel.

It added more complexity to the class that is already overloaded with a lot of ifs and was not working well always (like it was sometimes looping focus between those two states when holding ui_focus_prev action instead of leaving the control and switching to the hex LineEdit. I thought that adding another control for both drawing the focus stylebox and making switching focus more reliable is a better choice.

If it's better to have few more ifs than using this empty control, I can work on it again.

Copy link
Member

Choose a reason for hiding this comment

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

#99515 makes the code cleaner.
If your PR happens to be merged first, I'll simplify it and integrate with my changes.

scene/gui/color_picker.cpp Outdated Show resolved Hide resolved
scene/gui/color_picker.h Outdated Show resolved Hide resolved
scene/gui/color_picker.cpp Outdated Show resolved Hide resolved
scene/gui/color_picker.cpp Outdated Show resolved Hide resolved
} else if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) {
Vector2 center = c->get_size() / 2.0;

// HACK: It's a hack, as it messes up if I calculate it this way always.
Copy link
Member

@KoBeWi KoBeWi Dec 3, 2024

Choose a reason for hiding this comment

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

What does HACK refer to? Vector2i() is initial value of the cursor vector, so it needs to be initialized. Not sure what the other condition is for, it can be safely removed it seems.

Copy link
Author

@FeniXb3 FeniXb3 Dec 11, 2024

Choose a reason for hiding this comment

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

At first, I wanted to save the cursor position calculated in _hsv_draw and use it in _uv_input, but the cursor was moving in strange directions, as I mentioned in another comment. I checked if calculating it here every time would fix the issue, but it didn't. That's why I decided to calculate it only if it was not set (or was reset by clicking with mouse) and this is what the hack refers to, as it is not the way I think it should be done, but it does not seem to work well with the correct way. I might

Not sure what the other condition is for, it can be safely removed it seems.

I added it when I was working on sliding along the edge of the circle when you keep pressing any direction. But it seems it is not needed now. Removed.

Comment on lines +120 to +121
// TODO: Think about better name or a way to not use it at all
Vector2i hsv_keyboard_picker_cursor_position;
Copy link
Member

Choose a reason for hiding this comment

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

A way to not use it is keeping the variable local in _uv_input(), which means calculating sin/cos again every time.

Copy link
Author

Choose a reason for hiding this comment

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

The hack mentioned in another place is connected with issues with calculating sin/cos again every time. I tried it at the beginning and it's behaviour was strange - the cursor was not going in straight lines. I believe it might be caused by some precision loss while calculating this, or something like that.

I realized that even when you drag the cursor with mouse, the offset of it's center and the mouse cursor is changing, depending on how high or low on the circle you are. You can see that on this video:

Nagrywanie.ekranu.2024-12-09.091855.mp4

@FeniXb3
Copy link
Author

FeniXb3 commented Dec 7, 2024

#82979 could be used for better joypad navigation if it was merged. For now the only way is implement the behavior using NOTIFICATION_PROCESS manually, like it was done in Slider for example.

I did it like in Slider, thanks for the suggestion. I tried with get_vector in NOTIFICATION_INTERNAL_PROCESS before, but it was not working. Now I see that I might have missed calling set_process_internal(true) in the right moment.

Though the joypad navigation is a bit janky right now. When you enter the main color box, the only way out is either accepting or canceling; you can no longer select another element. There could be 2 focus modes here: one for navigating the elements and one for changing colors using the elements. You could switch them with accept/cancel. We have that in LineEdit. Though it's something that can be added later.

I added this second focus mode like in LineEdit. I thought about it before, but I didn't see benefits in my usecase so I skipped it before. It works well, for both LineEdit and ColorPicker picker shapes now if you use a ColorPicker directly. But if you show it via ColorPickerButton, it failes in both cases. The reason is that both accept and cancel actions close the popup that ColorPicker is in. You can see the behaviour on this video:

2024-12-07.20-37-02.ColorPicker.shape.focus.like.LineEdit.mp4

I didn't bother to implement this as I use focus next/prev actions to navigate through ColorPicker's controls. I configured inputs for those actions to shoulder buttons and I was using them to test this feature. I forgot that by default there are no joypad buttons connected to those actions. Is there any reason this is not done by default? I wonder if I can simply configure them within this PR to make it work better out of the box.

The wheel is difficult to use. image You need to press the direction depending on the current rotation. It would be easier if you could press a button to start moving and then it would rotate as long as it's still pressed, without needing to press other buttons.

Done. I had it stashed, as I mentioned in one of the previous comments here, as I wasn't sure if I should try and implement similar behaviour for both circle shapes. In the end I separated their behaviours.

@KoBeWi
Copy link
Member

KoBeWi commented Dec 7, 2024

The reason is that both accept and cancel actions close the popup that ColorPicker is in.

You could try using accept_event() in the shape's gui input, to prevent the event from propagating.

@FeniXb3
Copy link
Author

FeniXb3 commented Dec 7, 2024

The reason is that both accept and cancel actions close the popup that ColorPicker is in.

You could try using accept_event() in the shape's gui input, to prevent the event from propagating.

I do. What I see now while debugging is that the ColorPickerPopupPanel::_input_from_window gets the input event before my gui_input handler so the accept_event() don't do much in this case. As tihs popup has no "OK" button, I can understand that accept action is a reasonable way to confirm the color choice,

@FeniXb3 FeniXb3 requested a review from KoBeWi December 11, 2024 22:39
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.

7 participants