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

Improves some text field behavior. #8118

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion OpenRA.Game/Game.cs
Expand Up @@ -159,7 +159,6 @@ internal static void StartGame(string mapUID, WorldType type)
return;

Ui.MouseFocusWidget = null;
Ui.KeyboardFocusWidget = null;

OrderManager.LocalFrameNumber = 0;
OrderManager.LastTickTime = RunTime;
Expand Down
75 changes: 60 additions & 15 deletions OpenRA.Game/Widgets/Widget.cs
Expand Up @@ -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()
Expand Down Expand Up @@ -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);
}
Expand All @@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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()
Copy link
Member

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.

Copy link
Contributor Author

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.

{
return true;
}

public virtual bool YieldKeyboardFocus()
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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"; }
Expand Down Expand Up @@ -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())
Expand Down
1 change: 0 additions & 1 deletion OpenRA.Mods.Common/Widgets/ConfirmationDialogs.cs
Expand Up @@ -93,7 +93,6 @@ public static void CancelPrompt(string title, string text, Action onCancel = nul
cancelButton.OnClick();
return true;
};
input.TakeKeyboardFocus();
input.CursorPosition = input.Text.Length;
input.OnTextEdited = () => doValidate();

Expand Down
32 changes: 21 additions & 11 deletions OpenRA.Mods.Common/Widgets/Logic/AssetBrowserLogic.cs
Expand Up @@ -52,6 +52,16 @@ public AssetBrowserLogic(Widget widget, Action onExit, World world)
panel = widget;
assetSource = GlobalFileSystem.MountedFolders.First();

var closeButton = panel.GetOrNull<ButtonWidget>("CLOSE_BUTTON");
if (closeButton != null)
closeButton.OnClick = () =>
{
if (isVideoLoaded)
player.Stop();
Ui.CloseWindow();
onExit();
};

var ticker = panel.GetOrNull<LogicTickerWidget>("ANIMATION_TICKER");
if (ticker != null)
{
Expand Down Expand Up @@ -110,8 +120,18 @@ public AssetBrowserLogic(Widget widget, Action onExit, World world)

filenameInput = panel.Get<TextFieldWidget>("FILENAME_INPUT");
filenameInput.OnTextEdited = () => ApplyFilter(filenameInput.Text);
filenameInput.OnEscKey = filenameInput.YieldKeyboardFocus;
filenameInput.OnEscKey = () =>
{
if (filenameInput.Text.Length == 0)
closeButton.OnClick();
else
{
filenameInput.Text = null;
filenameInput.OnTextEdited();
}

return true;
};
var frameContainer = panel.GetOrNull("FRAME_SELECTOR");
if (frameContainer != null)
frameContainer.IsVisible = () => (currentSprites != null && currentSprites.Length > 1) ||
Expand Down Expand Up @@ -215,16 +235,6 @@ public AssetBrowserLogic(Widget widget, Action onExit, World world)
assetList = panel.Get<ScrollPanelWidget>("ASSET_LIST");
template = panel.Get<ScrollItemWidget>("ASSET_TEMPLATE");
PopulateAssetList();

var closeButton = panel.GetOrNull<ButtonWidget>("CLOSE_BUTTON");
if (closeButton != null)
closeButton.OnClick = () =>
{
if (isVideoLoaded)
player.Stop();
Ui.CloseWindow();
onExit();
};
}

void SelectNextFrame()
Expand Down
1 change: 0 additions & 1 deletion OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameChatLogic.cs
Expand Up @@ -156,7 +156,6 @@ public void OpenChat()
chatText.Text = "";
chatChrome.Visible = true;
chatScrollPanel.ScrollToBottom();
chatText.TakeKeyboardFocus();
if (!inDialog)
chatOverlay.Visible = false;
}
Expand Down
12 changes: 9 additions & 3 deletions OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs
Expand Up @@ -523,9 +523,6 @@ void CloseWindow()

chatLabel = lobby.Get<LabelWidget>("LABEL_CHATTYPE");
var chatTextField = lobby.Get<TextFieldWidget>("CHAT_TEXTFIELD");

chatTextField.TakeKeyboardFocus();

chatTextField.OnEnterKey = () =>
{
if (chatTextField.Text.Length == 0)
Expand All @@ -549,6 +546,15 @@ void CloseWindow()
else
return true;
};
chatTextField.OnEscKey = () =>
{
if (chatTextField.Text.Length == 0)
disconnectButton.OnClick();
else
chatTextField.Text = null;

return true;
};

chatPanel = lobby.Get<ScrollPanelWidget>("CHAT_DISPLAY");
chatTemplate = chatPanel.Get("CHAT_TEMPLATE");
Expand Down
27 changes: 16 additions & 11 deletions OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyUtils.cs
Expand Up @@ -257,23 +257,28 @@ public static void SetupEditableNameWidget(Widget parent, Session.Slot s, Sessio
name.IsDisabled = () => orderManager.LocalClient.IsReady;

name.Text = c.Name;
name.OnLoseFocus = () =>
name.OnEscKey = () =>
{
name.Text = c.Name;
name.YieldKeyboardFocus();
return true;
};
name.OnEnterKey = () =>
{
name.Text = name.Text.Trim();
if (name.Text.Length == 0)
name.Text = c.Name;
else if (name.Text != c.Name)
{
name.Text = Settings.SanitizedPlayerName(name.Text);
orderManager.IssueOrder(Order.Command("name " + name.Text));
Game.Settings.Player.Name = name.Text;
Game.Settings.Save();
}

if (name.Text == c.Name)
return;

name.Text = Settings.SanitizedPlayerName(name.Text);

orderManager.IssueOrder(Order.Command("name " + name.Text));
Game.Settings.Player.Name = name.Text;
Game.Settings.Save();
name.YieldKeyboardFocus();
return true;
};

name.OnEnterKey = () => { name.YieldKeyboardFocus(); return true; };
}

public static void SetupNameWidget(Widget parent, Session.Slot s, Session.Client c)
Expand Down
1 change: 0 additions & 1 deletion OpenRA.Mods.Common/Widgets/Logic/MapChooserLogic.cs
Expand Up @@ -58,7 +58,6 @@ internal MapChooserLogic(Widget widget, string initialMap, MapClassification ini
var mapFilterInput = widget.GetOrNull<TextFieldWidget>("MAPFILTER_INPUT");
if (mapFilterInput != null)
{
mapFilterInput.TakeKeyboardFocus();
mapFilterInput.OnEscKey = () =>
{
if (mapFilterInput.Text.Length == 0)
Expand Down
44 changes: 28 additions & 16 deletions OpenRA.Mods.Common/Widgets/Logic/SettingsLogic.cs
Expand Up @@ -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 = () =>
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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:

In OpenRA.Mods.Common/Widgets/Logic/SettingsLogic.cs
#8118 (comment):

        {
            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 = () =>
    

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.


Reply to this email directly or view it on GitHub
https://github.com/OpenRA/OpenRA/pull/8118/files#r31881309.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How should it be done otherwise?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

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

How should it be done otherwise?

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
Expand All @@ -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");
Expand Down Expand Up @@ -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 () => { };
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apparently this code block was unnecessary :/

}

Action ResetInputPanel(Widget panel)
Expand Down
4 changes: 2 additions & 2 deletions OpenRA.Mods.Common/Widgets/TextFieldWidget.cs
Expand Up @@ -222,12 +222,12 @@ public override bool HandleTextInput(string text)
bool wasDisabled;
public override void Tick()
{
// Remove the blicking cursor when disabled
// Remove the blinking cursor when disabled
var isDisabled = IsDisabled();
if (isDisabled != wasDisabled)
{
wasDisabled = isDisabled;
if (isDisabled && Ui.KeyboardFocusWidget == this)
if (isDisabled && Ui.GetKeyboardFocus() == this)
YieldKeyboardFocus();
}

Expand Down
1 change: 1 addition & 0 deletions mods/ra/chrome/assetbrowser.yaml
Expand Up @@ -60,6 +60,7 @@ Background@ASSETBROWSER_PANEL:
Y: 395
Width: 160
Height: 25
FocusPriorityDefault: 10
Label@PALETTE_DESC:
X: PARENT_RIGHT-WIDTH-270
Y: 60
Expand Down