diff options
Diffstat (limited to 'Ryujinx/Ui/Applet')
-rw-r--r-- | Ryujinx/Ui/Applet/GtkDynamicTextInputHandler.cs | 108 | ||||
-rw-r--r-- | Ryujinx/Ui/Applet/GtkHostUiHandler.cs | 25 | ||||
-rw-r--r-- | Ryujinx/Ui/Applet/GtkHostUiTheme.cs | 90 |
3 files changed, 222 insertions, 1 deletions
diff --git a/Ryujinx/Ui/Applet/GtkDynamicTextInputHandler.cs b/Ryujinx/Ui/Applet/GtkDynamicTextInputHandler.cs new file mode 100644 index 00000000..92e99385 --- /dev/null +++ b/Ryujinx/Ui/Applet/GtkDynamicTextInputHandler.cs @@ -0,0 +1,108 @@ +using Gtk; +using Ryujinx.HLE.Ui; +using Ryujinx.Input.GTK3; +using Ryujinx.Ui.Widgets; +using System.Threading; + +namespace Ryujinx.Ui.Applet +{ + /// <summary> + /// Class that forwards key events to a GTK Entry so they can be processed into text. + /// </summary> + internal class GtkDynamicTextInputHandler : IDynamicTextInputHandler + { + private readonly Window _parent; + private readonly OffscreenWindow _inputToTextWindow = new OffscreenWindow(); + private readonly RawInputToTextEntry _inputToTextEntry = new RawInputToTextEntry(); + + private bool _canProcessInput; + + public event DynamicTextChangedHandler TextChangedEvent; + public event KeyPressedHandler KeyPressedEvent; + public event KeyReleasedHandler KeyReleasedEvent; + + public bool TextProcessingEnabled + { + get + { + return Volatile.Read(ref _canProcessInput); + } + + set + { + Volatile.Write(ref _canProcessInput, value); + } + } + + public GtkDynamicTextInputHandler(Window parent) + { + _parent = parent; + _parent.KeyPressEvent += HandleKeyPressEvent; + _parent.KeyReleaseEvent += HandleKeyReleaseEvent; + + _inputToTextWindow.Add(_inputToTextEntry); + + _inputToTextEntry.TruncateMultiline = true; + + // Start with input processing turned off so the text box won't accumulate text + // if the user is playing on the keyboard. + _canProcessInput = false; + } + + [GLib.ConnectBefore()] + private void HandleKeyPressEvent(object o, KeyPressEventArgs args) + { + var key = (Common.Configuration.Hid.Key)GTK3MappingHelper.ToInputKey(args.Event.Key); + + if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true)) + { + return; + } + + if (_canProcessInput) + { + _inputToTextEntry.SendKeyPressEvent(o, args); + _inputToTextEntry.GetSelectionBounds(out int selectionStart, out int selectionEnd); + TextChangedEvent?.Invoke(_inputToTextEntry.Text, selectionStart, selectionEnd, _inputToTextEntry.OverwriteMode); + } + } + + [GLib.ConnectBefore()] + private void HandleKeyReleaseEvent(object o, KeyReleaseEventArgs args) + { + var key = (Common.Configuration.Hid.Key)GTK3MappingHelper.ToInputKey(args.Event.Key); + + if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true)) + { + return; + } + + if (_canProcessInput) + { + // TODO (caian): This solution may have problems if the pause is sent after a key press + // and before a key release. But for now GTK Entry does not seem to use release events. + _inputToTextEntry.SendKeyReleaseEvent(o, args); + _inputToTextEntry.GetSelectionBounds(out int selectionStart, out int selectionEnd); + TextChangedEvent?.Invoke(_inputToTextEntry.Text, selectionStart, selectionEnd, _inputToTextEntry.OverwriteMode); + } + } + + public void SetText(string text, int cursorBegin) + { + _inputToTextEntry.Text = text; + _inputToTextEntry.Position = cursorBegin; + } + + public void SetText(string text, int cursorBegin, int cursorEnd) + { + _inputToTextEntry.Text = text; + _inputToTextEntry.SelectRegion(cursorBegin, cursorEnd); + } + + public void Dispose() + { + _parent.KeyPressEvent -= HandleKeyPressEvent; + _parent.KeyReleaseEvent -= HandleKeyReleaseEvent; + } + } +}
\ No newline at end of file diff --git a/Ryujinx/Ui/Applet/GtkHostUiHandler.cs b/Ryujinx/Ui/Applet/GtkHostUiHandler.cs index c227ebd3..d81cbe3c 100644 --- a/Ryujinx/Ui/Applet/GtkHostUiHandler.cs +++ b/Ryujinx/Ui/Applet/GtkHostUiHandler.cs @@ -1,10 +1,11 @@ using Gtk; -using Ryujinx.HLE; using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; +using Ryujinx.HLE.Ui; using Ryujinx.Ui.Widgets; using System; using System.Threading; +using Action = System.Action; namespace Ryujinx.Ui.Applet { @@ -12,9 +13,13 @@ namespace Ryujinx.Ui.Applet { private readonly Window _parent; + public IHostUiTheme HostUiTheme { get; } + public GtkHostUiHandler(Window parent) { _parent = parent; + + HostUiTheme = new GtkHostUiTheme(parent); } public bool DisplayMessageDialog(ControllerAppletUiArgs args) @@ -186,5 +191,23 @@ namespace Ryujinx.Ui.Applet return showDetails; } + + private void SynchronousGtkInvoke(Action action) + { + var waitHandle = new ManualResetEventSlim(); + + Application.Invoke(delegate + { + action(); + waitHandle.Set(); + }); + + waitHandle.Wait(); + } + + public IDynamicTextInputHandler CreateDynamicTextInputHandler() + { + return new GtkDynamicTextInputHandler(_parent); + } } }
\ No newline at end of file diff --git a/Ryujinx/Ui/Applet/GtkHostUiTheme.cs b/Ryujinx/Ui/Applet/GtkHostUiTheme.cs new file mode 100644 index 00000000..f25da47c --- /dev/null +++ b/Ryujinx/Ui/Applet/GtkHostUiTheme.cs @@ -0,0 +1,90 @@ +using Gtk; +using Ryujinx.HLE.Ui; +using System.Diagnostics; + +namespace Ryujinx.Ui.Applet +{ + internal class GtkHostUiTheme : IHostUiTheme + { + private const int RenderSurfaceWidth = 32; + private const int RenderSurfaceHeight = 32; + + public string FontFamily { get; private set; } + + public ThemeColor DefaultBackgroundColor { get; } + public ThemeColor DefaultForegroundColor { get; } + public ThemeColor DefaultBorderColor { get; } + public ThemeColor SelectionBackgroundColor { get; } + public ThemeColor SelectionForegroundColor { get; } + + public GtkHostUiTheme(Window parent) + { + Entry entry = new Entry(); + entry.SetStateFlags(StateFlags.Selected, true); + + // Get the font and some colors directly from GTK. + FontFamily = entry.PangoContext.FontDescription.Family; + + // Get foreground colors from the style context. + + var defaultForegroundColor = entry.StyleContext.GetColor(StateFlags.Normal); + var selectedForegroundColor = entry.StyleContext.GetColor(StateFlags.Selected); + + DefaultForegroundColor = new ThemeColor((float) defaultForegroundColor.Alpha, (float) defaultForegroundColor.Red, (float) defaultForegroundColor.Green, (float) defaultForegroundColor.Blue); + SelectionForegroundColor = new ThemeColor((float)selectedForegroundColor.Alpha, (float)selectedForegroundColor.Red, (float)selectedForegroundColor.Green, (float)selectedForegroundColor.Blue); + + ListBoxRow row = new ListBoxRow(); + row.SetStateFlags(StateFlags.Selected, true); + + // Request the main thread to render some UI elements to an image to get an approximation for the color. + // NOTE (caian): This will only take the color of the top-left corner of the background, which may be incorrect + // if someone provides a custom style with a gradient or image. + + using (var surface = new Cairo.ImageSurface(Cairo.Format.Argb32, RenderSurfaceWidth, RenderSurfaceHeight)) + using (var context = new Cairo.Context(surface)) + { + context.SetSourceRGBA(1, 1, 1, 1); + context.Rectangle(0, 0, RenderSurfaceWidth, RenderSurfaceHeight); + context.Fill(); + + // The background color must be from the main Window because entry uses a different color. + parent.StyleContext.RenderBackground(context, 0, 0, RenderSurfaceWidth, RenderSurfaceHeight); + + DefaultBackgroundColor = ToThemeColor(surface.Data); + + context.SetSourceRGBA(1, 1, 1, 1); + context.Rectangle(0, 0, RenderSurfaceWidth, RenderSurfaceHeight); + context.Fill(); + + // Use the background color of the list box row when selected as the text box frame color because they are the + // same in the default theme. + row.StyleContext.RenderBackground(context, 0, 0, RenderSurfaceWidth, RenderSurfaceHeight); + + DefaultBorderColor = ToThemeColor(surface.Data); + } + + // Use the border color as the text selection color. + SelectionBackgroundColor = DefaultBorderColor; + } + + private ThemeColor ToThemeColor(byte[] data) + { + Debug.Assert(data.Length == 4 * RenderSurfaceWidth * RenderSurfaceHeight); + + // Take the center-bottom pixel of the surface. + int position = 4 * (RenderSurfaceWidth * (RenderSurfaceHeight - 1) + RenderSurfaceWidth / 2); + + if (position + 4 > data.Length) + { + return new ThemeColor(1, 0, 0, 0); + } + + float a = data[position + 3] / 255.0f; + float r = data[position + 2] / 255.0f; + float g = data[position + 1] / 255.0f; + float b = data[position + 0] / 255.0f; + + return new ThemeColor(a, r, g, b); + } + } +}
\ No newline at end of file |