Skip to content

Commit

Permalink
feat: [macOS] Add support for TextBox
Browse files Browse the repository at this point in the history
  • Loading branch information
ajpinedam authored and jeromelaban committed Apr 8, 2020
1 parent 4ff5896 commit 8c99072
Show file tree
Hide file tree
Showing 22 changed files with 729 additions and 103 deletions.
File renamed without changes.
80 changes: 80 additions & 0 deletions src/Uno.UI/Controls/Window.macOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,92 @@ namespace Uno.UI.Controls
/// </summary>
public partial class Window : NSWindow
{
private static readonly WeakAttachedDictionary<NSView, string> _attachedProperties = new WeakAttachedDictionary<NSView, string>();
private const string NeedsKeyboardAttachedPropertyKey = "NeedsKeyboard";

private readonly InputPane _inputPane;
private WeakReference<NSScrollView> _scrollViewModifiedForKeyboard;

/// <summary>
/// ctor.
/// </summary>
public Window(CGRect contentRect, NSWindowStyle aStyle, NSBackingStore bufferingType, bool deferCreation)
: base(contentRect, aStyle, bufferingType, deferCreation)
{
_inputPane = InputPane.GetForCurrentView();
_inputPane.Window = this;
}

public static void SetNeedsKeyboard(NSView view, bool needsKeyboard)
{
if (view != null)
{
_attachedProperties.SetValue(view, NeedsKeyboardAttachedPropertyKey, (bool?)needsKeyboard);
}
}

private static bool GetNeedsKeyboard(NSView view)
{
var superViews = view.FindSuperviews().ToList();
superViews.Insert(0, view);

return superViews.Any(superView => _attachedProperties.GetValue(superView, NeedsKeyboardAttachedPropertyKey, () => default(bool?)).GetValueOrDefault());
}

private static bool NeedsKeyboard(NSView view)
{
return view is NSTextView
|| view is NSTextField
|| GetNeedsKeyboard(view);
}

public BringIntoViewMode? FocusedViewBringIntoViewOnKeyboardOpensMode { get; set; }


internal void MakeVisible(NSView view, BringIntoViewMode? bringIntoViewMode, bool useForcedAnimation = false)
{
if (view == null)
{
return;
}

if (bringIntoViewMode == null)
{
return;
}

var scrollView = view.FindSuperviewsOfType<NSScrollView>().LastOrDefault();
_scrollViewModifiedForKeyboard = new WeakReference<NSScrollView>(scrollView);

if (scrollView == null)
{
this.Log().Warn("Keyboard will show, but we cannot find any ScrollView with enough space for the currently focused view, so it's impossible to ensure that it's visible.");
return;
}


var scrollViewRectInWindow = scrollView.ConvertRectFromView(scrollView.Bounds, scrollView);

var keyboardTop = (nfloat)_inputPane.OccludedRect.Top;

var keyboardOverlap = scrollViewRectInWindow.Bottom - keyboardTop;
if (keyboardOverlap > 0)
{
scrollView.ContentInsets = new NSEdgeInsets(0, 0, keyboardOverlap, 0);
}

var viewRectInScrollView = CGRect.Empty;

if (!(view is TextBox))
{
// We want to scroll to the textbox and not the inner textview.
view = view.FindFirstParent<TextBox>() ?? view;
}

viewRectInScrollView = scrollView.ConvertRectFromView(view.Bounds, view);

scrollView.ScrollRectToVisible(viewRectInScrollView);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public partial class TextBox
// Skipping already declared property TextWrapping
// Skipping already declared property TextAlignment
// Skipping already declared property Text
#if false || false || NET461 || false || __MACOS__
#if false || false || NET461 || false || false
[global::Uno.NotImplemented]
public int SelectionStart
{
Expand All @@ -24,7 +24,7 @@ public int SelectionStart
}
}
#endif
#if false || false || NET461 || false || __MACOS__
#if false || false || NET461 || false || false
[global::Uno.NotImplemented]
public int SelectionLength
{
Expand Down
8 changes: 3 additions & 5 deletions src/Uno.UI/Mixins/macOS/FrameworkElementMixins.tt
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@
<#@output extension="g.cs" #>
#if __MACOS__
<#
AddClass("Windows.UI.Xaml", "FrameworkElement", defineSetNeedsLayout: false, defineLayoutSubviews: false, hasAttachedToWindow: false, overridesAttachedToWindow: true, hasLayouter: true);
AddClass("Windows.UI.Xaml", "FrameworkElement", defineSetNeedsLayout: false, defineLayoutSubviews: false, hasAttachedToWindow: false, overridesAttachedToWindow: true, hasLayouter: true, hasAutomationPeer: true);
AddClass("Windows.UI.Xaml.Controls", "Image", overridesAttachedToWindow: false, hasAutomationPeer: true, hasLayouter: true);

// TODO
// AddClass("Windows.UI.Xaml.Controls", "ScrollContentPresenter", defineSetNeedsLayout: false, defineLayoutSubviews: false, hasAttachedToWindow: false, overridesAttachedToWindow: false);
AddClass("Windows.UI.Xaml.Controls", "ScrollContentPresenter", defineSetNeedsLayout: false, defineLayoutSubviews: false, hasAttachedToWindow: false, overridesAttachedToWindow: false);
// AddClass("Windows.UI.Xaml.Controls", "Picker", hasAttachedToWindow: true);
// AddClass("Windows.UI.Xaml.Controls", "NativeListViewBase", hasAttachedToWindow: false, overridesAttachedToWindow: true, defineSetNeedsLayout: false, defineLayoutSubviews: false);
// AddClass("Windows.UI.Xaml.Controls", "ProgressRing", hasAttachedToWindow: false, overridesAttachedToWindow: true);
// AddClass("Uno.UI.Controls.Legacy", "ListViewBase", hasAttachedToWindow: false, overridesAttachedToWindow: true, defineSetNeedsLayout: false, defineLayoutSubviews: false);
// AddClass("Windows.UI.Xaml.Controls", "SinglelineTextBoxView", isUIControl: true, hasAttachedToWindow: true, overridesAttachedToWindow: false, isNewBackground: true);
// AddClass("Windows.UI.Xaml.Controls", "MultilineTextBoxView", isUIControl: false, hasAttachedToWindow: true, overridesAttachedToWindow: false, isNewBackground: false);
AddClass("Windows.UI.Xaml.Controls", "TextBoxView", hasAutomationPeer: false, hasAttachedToWindow: true, overridesAttachedToWindow: false);
#>
<#@include file="..\..\UI\Xaml\IFrameworkElementImplementation.macOS.tt"#>
#endif
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
#if __IOS__
using System;
using System.Collections.Generic;
using System.Text;
using Uno.UI;
using Windows.Foundation;
using UIKit;
using Foundation;

#if __IOS__
using UIKit;
#endif

namespace Windows.UI.ViewManagement
{
public partial class InputPane
{
internal Uno.UI.Controls.Window Window { get; set; }

#if __IOS__
partial void TryHidePartial()
{
UIApplication.SharedApplication.KeyWindow.EndEditing(true);
UIKit.UIApplication.SharedApplication.KeyWindow.EndEditing(true);
}

internal Uno.UI.Controls.Window Window { get; set; }

partial void EnsureFocusedElementInViewPartial()
{
Expand All @@ -29,6 +33,6 @@ partial void EnsureFocusedElementInViewPartial()
Window?.RestoreFocusedViewVisibility();
}
}
#endif
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@
using System.Collections.Generic;
using System.Text;
using CoreGraphics;
using UIKit;

#if __IOS__
using _EdgeInsets = UIKit.UIEdgeInsets;
#elif __MACOS__
using _EdgeInsets = AppKit.NSEdgeInsets;
#endif

namespace Windows.UI.Xaml.Controls
{
internal partial interface IScrollContentPresenter : IUIScrollView
{
UIEdgeInsets ContentInset { get; set; }
_EdgeInsets ContentInset { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ namespace Windows.UI.Xaml.Controls
internal interface IUIScrollView
{
CGPoint UpperScrollLimit { get; }

void SetContentOffset(CGPoint contentOffset, bool animated);

#if __IOS__
void SetZoomScale(nfloat scale, bool animated);
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,17 @@ CGSize SizeThatFits(CGSize size)
{
if (_content != null)
{
var frameworkElement = _content as IFrameworkElement;
double horizontalMargin = 0;
double verticalMargin = 0;

var horizontalMargin = frameworkElement.Margin.Left + frameworkElement.Margin.Right;
var verticalMargin = frameworkElement.Margin.Top + frameworkElement.Margin.Bottom;
if (_content is IFrameworkElement frameworkElement)
{
horizontalMargin = frameworkElement.Margin.Left + frameworkElement.Margin.Right;
verticalMargin = frameworkElement.Margin.Top + frameworkElement.Margin.Bottom;
}

size = AdjustSize(size);


var availableSizeForChild = size;
if (!(_content is IFrameworkElement))
{
Expand Down Expand Up @@ -101,11 +104,10 @@ CGSize SizeThatFits(CGSize size)
}
}

public override void
#if __IOS__
LayoutSubviews()
public override void LayoutSubviews()
#else
Layout()
public override void Layout()
#endif
{
try
Expand All @@ -118,32 +120,41 @@ public override void
SizeThatFits(Frame.Size);
}

var frameworkElement = _content as IFrameworkElement;

var horizontalMargin = frameworkElement.Margin.Left + frameworkElement.Margin.Right;
var verticalMargin = frameworkElement.Margin.Top + frameworkElement.Margin.Bottom;
double horizontalMargin = 0;
double verticalMargin = 0;

var adjustedMeasure = new CGSize(
GetAdjustedArrangeWidth(frameworkElement, (nfloat)horizontalMargin),
GetAdjustedArrangeHeight(frameworkElement, (nfloat)verticalMargin)
);
var frameworkElement = _content as IFrameworkElement;

// Zoom works by applying a transform to the child view. If a view has a non-identity transform, its Frame shouldn't be set.
if (ZoomScale == 1)
if (frameworkElement != null)
{
_content.Frame = new CGRect(
GetAdjustedArrangeX(frameworkElement, adjustedMeasure, horizontalMargin),
GetAdjustedArrangeY(frameworkElement, adjustedMeasure, verticalMargin),
adjustedMeasure.Width,
adjustedMeasure.Height
horizontalMargin = frameworkElement.Margin.Left + frameworkElement.Margin.Right;
verticalMargin = frameworkElement.Margin.Top + frameworkElement.Margin.Bottom;

var adjustedMeasure = new CGSize(
GetAdjustedArrangeWidth(frameworkElement, (nfloat)horizontalMargin),
GetAdjustedArrangeHeight(frameworkElement, (nfloat)verticalMargin)
);

// Zoom works by applying a transform to the child view. If a view has a non-identity transform, its Frame shouldn't be set.
if (ZoomScale == 1)
{
_content.Frame = new CGRect(
GetAdjustedArrangeX(frameworkElement, adjustedMeasure, horizontalMargin),
GetAdjustedArrangeY(frameworkElement, adjustedMeasure, verticalMargin),
adjustedMeasure.Width,
adjustedMeasure.Height
);
}
}

#if __IOS__
ContentSize = AdjustContentSize(_content.Frame.Size + new CGSize(horizontalMargin, verticalMargin));

// This prevents unnecessary touch delays (which affects the pressed visual states of buttons) when user can't scroll.
UpdateDelayedTouches();
#else
var size = AdjustContentSize(_content.Frame.Size + new CGSize(horizontalMargin, verticalMargin));
ContentView.Frame = new CGRect(new CGPoint(0, 0), size);
#endif
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using AppKit;
using Uno.UI;
using Foundation;
using CoreGraphics;

namespace Windows.UI.Xaml.Controls
{
Expand All @@ -30,6 +31,11 @@ public nfloat ZoomScale {
get => Magnification;
set => Magnification = value;
}

public NSEdgeInsets ContentInset { get; set; }

public CGPoint UpperScrollLimit { get; }

public ScrollMode HorizontalScrollMode { get; set; }

public ScrollMode VerticalScrollMode { get; set; }
Expand Down Expand Up @@ -68,6 +74,11 @@ public override bool NeedsLayout
}
}

public void SetContentOffset(CGPoint contentOffset, bool animated)
{
// Support for ChangeView https://github.com/unoplatform/uno/issues/626
}

partial void OnContentChanged(NSView previousView, NSView newView)
{
DocumentView = newView;
Expand Down
3 changes: 3 additions & 0 deletions src/Uno.UI/UI/Xaml/Controls/ScrollViewer/ScrollViewer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
using View = UIKit.UIView;
using Color = UIKit.UIColor;
using Font = UIKit.UIFont;
#elif __MACOS__
using View = AppKit.NSView;
using AppKit;
#else
using View = Windows.UI.Xaml.UIElement;
#endif
Expand Down
Loading

0 comments on commit 8c99072

Please sign in to comment.