Skip to content

Commit

Permalink
Improves keyboard focus management.
Browse files Browse the repository at this point in the history
Also improves some text field behavior.
  • Loading branch information
deniz1a committed Jun 9, 2015
1 parent 8ccb557 commit c94dbfb
Show file tree
Hide file tree
Showing 16 changed files with 143 additions and 59 deletions.
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: 62 additions & 13 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,50 @@ public static bool HandleInput(MouseInput mi)
return handled;
}

static Widget GetHighestFocusPriority(Widget w)
{
if (w == null || !w.IsVisible())
return null;

var focus = w;
foreach (var child in w.Children)
{
if (!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 +169,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 +182,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 +236,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 +290,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 +324,39 @@ public virtual bool TakeKeyboardFocus()
if (HasKeyboardFocus)
return true;

if (Ui.KeyboardFocusWidget != null && !Ui.KeyboardFocusWidget.YieldKeyboardFocus())
return false;
var topPriority = 100;

Ui.KeyboardFocusWidget = this;
var f = Ui.GetKeyboardFocus();
if (f != null)
{
if (!f.TryYieldKeyboardFocus())
return false;

if (f.FocusPriority == topPriority)
f.FocusPriority = f.FocusPriorityDefault;
}

FocusPriority = topPriority;
return true;
}

public virtual bool TryYieldKeyboardFocus()
{
return true;
}

public virtual bool YieldKeyboardFocus()
{
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
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 = () =>
{
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 () => { };
}

Action ResetInputPanel(Widget panel)
Expand Down
2 changes: 1 addition & 1 deletion OpenRA.Mods.Common/Widgets/TextFieldWidget.cs
Expand Up @@ -227,7 +227,7 @@ public override void Tick()
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
1 change: 1 addition & 0 deletions mods/ra/chrome/confirmation-dialogs.yaml
Expand Up @@ -83,6 +83,7 @@ Background@TEXT_INPUT_PROMPT:
Y: 80
Width: PARENT_RIGHT - 40
Height: 25
FocusPriorityDefault: 10
Button@ACCEPT_BUTTON:
X: 20
Y: PARENT_BOTTOM - 45
Expand Down

0 comments on commit c94dbfb

Please sign in to comment.