-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
Improves some text field behavior. #8118
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,6 @@ public static class Ui | |
static readonly Stack<Widget> WindowList = new Stack<Widget>(); | ||
|
||
public static Widget MouseFocusWidget; | ||
public static Widget KeyboardFocusWidget; | ||
public static Widget MouseOverWidget; | ||
|
||
public static void CloseWindow() | ||
|
@@ -108,18 +107,47 @@ public static bool HandleInput(MouseInput mi) | |
return handled; | ||
} | ||
|
||
static Widget GetHighestFocusPriority(Widget w) | ||
{ | ||
var focus = w; | ||
foreach (var child in w.Children) | ||
{ | ||
if (child == null || !child.IsVisible()) | ||
continue; | ||
|
||
if (child.FocusPriority > focus.FocusPriority) | ||
focus = child; | ||
|
||
var r = GetHighestFocusPriority(child); | ||
if (r != null && r.FocusPriority > focus.FocusPriority) | ||
focus = r; | ||
} | ||
|
||
if (focus.FocusPriority < 1) | ||
return null; | ||
|
||
return focus; | ||
} | ||
|
||
public static Widget GetKeyboardFocus() | ||
{ | ||
return GetHighestFocusPriority(Root); | ||
} | ||
|
||
public static bool HandleKeyPress(KeyInput e) | ||
{ | ||
if (KeyboardFocusWidget != null) | ||
return KeyboardFocusWidget.HandleKeyPressOuter(e); | ||
var f = GetKeyboardFocus(); | ||
if (f != null) | ||
return f.HandleKeyPressOuter(e); | ||
|
||
return Root.HandleKeyPressOuter(e); | ||
} | ||
|
||
public static bool HandleTextInput(string text) | ||
{ | ||
if (KeyboardFocusWidget != null) | ||
return KeyboardFocusWidget.HandleTextInputOuter(text); | ||
var f = GetKeyboardFocus(); | ||
if (f != null) | ||
return f.HandleTextInputOuter(text); | ||
|
||
return Root.HandleTextInputOuter(text); | ||
} | ||
|
@@ -138,6 +166,7 @@ public abstract class Widget | |
public readonly List<Widget> Children = new List<Widget>(); | ||
|
||
// Info defined in YAML | ||
public readonly int FocusPriorityDefault; | ||
public string Id = null; | ||
public string X = "0"; | ||
public string Y = "0"; | ||
|
@@ -150,6 +179,7 @@ public abstract class Widget | |
public bool IgnoreChildMouseOver; | ||
|
||
// Calculated internally | ||
public int FocusPriority; | ||
public Rectangle Bounds; | ||
public Widget Parent = null; | ||
public Func<bool> IsVisible; | ||
|
@@ -203,6 +233,8 @@ public virtual Rectangle RenderBounds | |
|
||
public virtual void Initialize(WidgetArgs args) | ||
{ | ||
FocusPriority = FocusPriorityDefault; | ||
|
||
// Parse the YAML equations to find the widget bounds | ||
var parentBounds = (Parent == null) | ||
? new Rectangle(0, 0, Game.Renderer.Resolution.Width, Game.Renderer.Resolution.Height) | ||
|
@@ -255,7 +287,7 @@ public virtual Rectangle GetEventBounds() | |
} | ||
|
||
public bool HasMouseFocus { get { return Ui.MouseFocusWidget == this; } } | ||
public bool HasKeyboardFocus { get { return Ui.KeyboardFocusWidget == this; } } | ||
public bool HasKeyboardFocus { get { return Ui.GetKeyboardFocus() == this; } } | ||
|
||
public virtual bool TakeMouseFocus(MouseInput mi) | ||
{ | ||
|
@@ -289,25 +321,39 @@ public virtual bool TakeKeyboardFocus() | |
if (HasKeyboardFocus) | ||
return true; | ||
|
||
if (Ui.KeyboardFocusWidget != null && !Ui.KeyboardFocusWidget.YieldKeyboardFocus()) | ||
return false; | ||
var topPriority = 100; | ||
|
||
var f = Ui.GetKeyboardFocus(); | ||
if (f != null) | ||
{ | ||
if (!f.TryYieldKeyboardFocus()) | ||
return false; | ||
|
||
if (f.FocusPriority == topPriority) | ||
f.FocusPriority = f.FocusPriorityDefault; | ||
} | ||
|
||
FocusPriority = topPriority; | ||
return true; | ||
} | ||
|
||
Ui.KeyboardFocusWidget = this; | ||
public virtual bool TryYieldKeyboardFocus() | ||
{ | ||
return true; | ||
} | ||
|
||
public virtual bool YieldKeyboardFocus() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are YieldKeyboardFocus() and TakeKeyboardFocus() virtual? Shouldn't only TryYieldKeyboardFocus() be virtual so a subclass can add conditions for yielding focus? |
||
{ | ||
if (Ui.KeyboardFocusWidget == this) | ||
Ui.KeyboardFocusWidget = null; | ||
if (!TryYieldKeyboardFocus()) | ||
return false; | ||
|
||
FocusPriority = FocusPriorityDefault; | ||
return true; | ||
} | ||
|
||
void ForceYieldKeyboardFocus() | ||
{ | ||
if (Ui.KeyboardFocusWidget == this && !YieldKeyboardFocus()) | ||
Ui.KeyboardFocusWidget = null; | ||
FocusPriority = FocusPriorityDefault; | ||
} | ||
|
||
public virtual string GetCursor(int2 pos) { return "default"; } | ||
|
@@ -449,9 +495,8 @@ public virtual void RemoveChildren() | |
|
||
public virtual void Removed() | ||
{ | ||
// Using the forced versions because the widgets | ||
// Using the forced version because the widgets | ||
// have been removed | ||
ForceYieldKeyboardFocus(); | ||
ForceYieldMouseFocus(); | ||
|
||
foreach (var c in Children.OfType<Widget>().Reverse()) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -190,14 +190,21 @@ Action InitDisplayPanel(Widget panel) | |
|
||
var frameLimitTextfield = panel.Get<TextFieldWidget>("FRAME_LIMIT_TEXTFIELD"); | ||
frameLimitTextfield.Text = ds.MaxFramerate.ToString(); | ||
frameLimitTextfield.OnLoseFocus = () => | ||
frameLimitTextfield.OnEnterKey = () => | ||
{ | ||
int fps; | ||
Exts.TryParseIntegerInvariant(frameLimitTextfield.Text, out fps); | ||
ds.MaxFramerate = fps.Clamp(1, 1000); | ||
frameLimitTextfield.Text = ds.MaxFramerate.ToString(); | ||
frameLimitTextfield.YieldKeyboardFocus(); | ||
return true; | ||
}; | ||
frameLimitTextfield.OnEscKey = () => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When I press ESC I usually assume the GUI will remove the cursor, not switch to the last selection. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This resets the text field to last saved value and then removes the cursor. Should it save the current value when you press ESC? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd expect ESC to act like a Cancel button, not like Enter/OK/Apply. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh ok now I get what you mean. I'll try to fix that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @penev92: He means when you click the other text field while one of them has focus and then press ESC the other text field gets focus back. This is good in lobby but bad here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And in the lobby, if you alternatingly click on chat field and player name field, you are also adding them multiple times to the stack. But there it doesn't cause any problems because chat field doesn't yield focus when ESC is pressed, it just resets the text, so there is no focus switching with ESC. And when the lobby widget is removed, these text fields become null and stay on the stack and get cleaned up with the next YieldKeyboardFocus() call. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is pretty weird. On Mon, Jun 8, 2015 at 10:10 AM, Deniz notifications@github.com wrote:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How should it be done otherwise? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok this also happens in Input settings menu with hotkey assignment. If you keep clicking on different hotkey fields, they all get added to the stack and when you press ESC you switch focus in reverse order. So there has to be a way to prevent other text fields in the same widget from getting focus. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Convention is that TAB cycles input fields and ESC just leaves them imho. |
||
{ | ||
frameLimitTextfield.Text = ds.MaxFramerate.ToString(); | ||
frameLimitTextfield.YieldKeyboardFocus(); | ||
return true; | ||
}; | ||
frameLimitTextfield.OnEnterKey = () => { frameLimitTextfield.YieldKeyboardFocus(); return true; }; | ||
frameLimitTextfield.IsDisabled = () => !ds.CapFramerate; | ||
|
||
// Player profile | ||
|
@@ -206,11 +213,25 @@ Action InitDisplayPanel(Widget panel) | |
var nameTextfield = panel.Get<TextFieldWidget>("PLAYERNAME"); | ||
nameTextfield.IsDisabled = () => worldRenderer.World.Type != WorldType.Shellmap; | ||
nameTextfield.Text = Settings.SanitizedPlayerName(ps.Name); | ||
nameTextfield.OnEnterKey = () => { nameTextfield.YieldKeyboardFocus(); return true; }; | ||
nameTextfield.OnLoseFocus = () => | ||
nameTextfield.OnEnterKey = () => | ||
{ | ||
nameTextfield.Text = Settings.SanitizedPlayerName(nameTextfield.Text); | ||
ps.Name = nameTextfield.Text; | ||
nameTextfield.Text = nameTextfield.Text.Trim(); | ||
if (nameTextfield.Text.Length == 0) | ||
nameTextfield.Text = Settings.SanitizedPlayerName(ps.Name); | ||
else | ||
{ | ||
nameTextfield.Text = Settings.SanitizedPlayerName(nameTextfield.Text); | ||
ps.Name = nameTextfield.Text; | ||
} | ||
|
||
nameTextfield.YieldKeyboardFocus(); | ||
return true; | ||
}; | ||
nameTextfield.OnEscKey = () => | ||
{ | ||
nameTextfield.Text = Settings.SanitizedPlayerName(ps.Name); | ||
nameTextfield.YieldKeyboardFocus(); | ||
return true; | ||
}; | ||
|
||
var colorPreview = panel.Get<ColorPreviewManagerWidget>("COLOR_MANAGER"); | ||
|
@@ -475,16 +496,7 @@ Action InitInputPanel(Widget panel) | |
BindHotkeyPref(kv, ks, globalTemplate, hotkeyList); | ||
} | ||
|
||
return () => | ||
{ | ||
// Remove focus from the selected hotkey widget | ||
// This is a bit of a hack, but works | ||
if (Ui.KeyboardFocusWidget != null && panel.GetOrNull(Ui.KeyboardFocusWidget.Id) != null) | ||
{ | ||
Ui.KeyboardFocusWidget.YieldKeyboardFocus(); | ||
Ui.KeyboardFocusWidget = null; | ||
} | ||
}; | ||
return () => { }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apparently this code block was unnecessary :/ |
||
} | ||
|
||
Action ResetInputPanel(Widget panel) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be completely unused outside this class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that is a template method. A widget can overwrite it and define custom conditions to prevent others from taking focus. This was partially implemented in YieldKeyboardFocus() and used here: https://github.com/OpenRA/OpenRA/pull/8118/files#diff-5a71e6a689956fb5a5f3315e795c9a75L292 . I moved that to this method to make it clearer.