Skip to content

Commit bfacd86

Browse files
committed
feat(ios): RequestedTheme will now affects KeyboardAppearance
1 parent 78f4dad commit bfacd86

File tree

8 files changed

+200
-3
lines changed

8 files changed

+200
-3
lines changed

src/SamplesApp/UITests.Shared/UITests.Shared.projitems

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,6 +3245,10 @@
32453245
<SubType>Designer</SubType>
32463246
<Generator>MSBuild:Compile</Generator>
32473247
</Page>
3248+
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Input\Keyboard\Keyboard_iOS_Theme.xaml">
3249+
<SubType>Designer</SubType>
3250+
<Generator>MSBuild:Compile</Generator>
3251+
</Page>
32483252
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Input\Keyboard\Keyboard_Showing_Dismissal.xaml">
32493253
<SubType>Designer</SubType>
32503254
<Generator>MSBuild:Compile</Generator>
@@ -6380,6 +6384,9 @@
63806384
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Input\Keyboard\Keyboard_Events.xaml.cs">
63816385
<DependentUpon>Keyboard_Events.xaml</DependentUpon>
63826386
</Compile>
6387+
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Input\Keyboard\Keyboard_iOS_Theme.xaml.cs">
6388+
<DependentUpon>Keyboard_iOS_Theme.xaml</DependentUpon>
6389+
</Compile>
63836390
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Input\Keyboard\Keyboard_Showing_Dismissal.xaml.cs">
63846391
<DependentUpon>Keyboard_Showing_Dismissal.xaml</DependentUpon>
63856392
</Compile>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<Page x:Class="UITests.Windows_UI_Xaml_Input.Keyboard.Keyboard_iOS_Theme"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:local="using:UITests.Windows_UI_Xaml_Input.Keyboard"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
7+
xmlns:ios="http://uno.ui/ios"
8+
mc:Ignorable="d ios"
9+
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
10+
11+
<ScrollViewer>
12+
<StackPanel>
13+
<TextBlock Text="Requested Theme:" />
14+
<Grid>
15+
<Grid.ColumnDefinitions>
16+
<ColumnDefinition Width="*" />
17+
<ColumnDefinition Width="*" />
18+
<ColumnDefinition Width="*" />
19+
</Grid.ColumnDefinitions>
20+
21+
<RadioButton Grid.Column="0" Content="Default" Click="UpdateTheme" HorizontalAlignment="Stretch" />
22+
<RadioButton Grid.Column="1" Content="Light" Click="UpdateTheme" HorizontalAlignment="Stretch" />
23+
<RadioButton Grid.Column="2" Content="Dark" Click="UpdateTheme" HorizontalAlignment="Stretch" />
24+
</Grid>
25+
26+
<TextBlock Text="KB Appearance: for 'custom' text/password-box" />
27+
<Grid>
28+
<Grid.ColumnDefinitions>
29+
<ColumnDefinition Width="*" />
30+
<ColumnDefinition Width="*" />
31+
<ColumnDefinition Width="*" />
32+
</Grid.ColumnDefinitions>
33+
34+
<RadioButton Grid.Column="0" Content="Default" Click="UpdateKeyboardAppearance" HorizontalAlignment="Stretch" />
35+
<RadioButton Grid.Column="1" Content="Light" Click="UpdateKeyboardAppearance" HorizontalAlignment="Stretch" />
36+
<RadioButton Grid.Column="2" Content="Dark" Click="UpdateKeyboardAppearance" HorizontalAlignment="Stretch" />
37+
</Grid>
38+
<TextBlock Text="note: Dark will be displayed as Alert because it shared the same value."
39+
TextWrapping="WrapWholeWords" />
40+
41+
<StackPanel x:Name="TestPanel">
42+
<TextBlock Text="TextBox" Margin="0,20,0,0" />
43+
<TextBox PlaceholderText="default" />
44+
<TextBox PlaceholderText="light" ios:KeyboardAppearance="Light" />
45+
<TextBox PlaceholderText="dark" ios:KeyboardAppearance="Dark" />
46+
<TextBox PlaceholderText="custom:" />
47+
48+
<TextBlock Text="PasswordBox" Margin="0,20,0,0" />
49+
<PasswordBox PlaceholderText="default" />
50+
<PasswordBox PlaceholderText="light" ios:KeyboardAppearance="Light" />
51+
<PasswordBox PlaceholderText="dark" ios:KeyboardAppearance="Dark" />
52+
<PasswordBox PlaceholderText="custom:" />
53+
</StackPanel>
54+
55+
<TextBlock TextWrapping="WrapWholeWords">
56+
<Run>The 'device theme' can be changed by (ios)Settings &gt; Developer &gt; Dark Appearance.</Run><LineBreak /><LineBreak />
57+
<Run>'default' should be affected by requested theme or os theme.</Run><LineBreak />
58+
<Run>'dark/light' should always be that theme.</Run><LineBreak />
59+
<Run>'custom' should be affected by 'KB Appearance', requested theme or os theme.</Run><LineBreak />
60+
</TextBlock>
61+
</StackPanel>
62+
</ScrollViewer>
63+
</Page>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Runtime.InteropServices.WindowsRuntime;
6+
using Uno.UI.Samples.Controls;
7+
using Windows.Foundation;
8+
using Windows.Foundation.Collections;
9+
using Windows.UI.Xaml;
10+
using Windows.UI.Xaml.Controls;
11+
using Windows.UI.Xaml.Controls.Primitives;
12+
using Windows.UI.Xaml.Data;
13+
using Windows.UI.Xaml.Input;
14+
using Windows.UI.Xaml.Media;
15+
using Windows.UI.Xaml.Navigation;
16+
17+
namespace UITests.Windows_UI_Xaml_Input.Keyboard
18+
{
19+
[SampleControlInfo("Keyboard", nameof(Keyboard_iOS_Theme),
20+
description: Description,
21+
ignoreInSnapshotTests: true,
22+
isManualTest: true)]
23+
public sealed partial class Keyboard_iOS_Theme : Page
24+
{
25+
private const string Description = "[iOS-only] Keyboard theme should be determined based on the following precedences: KeyboardAppearance > RequestedTheme > Device Theme.";
26+
27+
public Keyboard_iOS_Theme()
28+
{
29+
this.InitializeComponent();
30+
}
31+
32+
private void UpdateTheme(object sender, RoutedEventArgs e)
33+
{
34+
var root = global::Windows.UI.Xaml.Window.Current.Content as FrameworkElement;
35+
var theme = (sender as RadioButton).Content switch
36+
{
37+
"Light" => ElementTheme.Light,
38+
"Dark" => ElementTheme.Dark,
39+
"Default" => ElementTheme.Default,
40+
41+
_ => throw new ArgumentOutOfRangeException()
42+
};
43+
44+
root.RequestedTheme = theme;
45+
}
46+
47+
private void UpdateKeyboardAppearance(object sender, RoutedEventArgs e)
48+
{
49+
#if __IOS__
50+
var appearance = (sender as RadioButton).Content switch
51+
{
52+
"Light" => UIKit.UIKeyboardAppearance.Light,
53+
"Dark" => UIKit.UIKeyboardAppearance.Dark,
54+
"Default" => UIKit.UIKeyboardAppearance.Default,
55+
56+
_ => throw new ArgumentOutOfRangeException()
57+
};
58+
foreach (var item in TestPanel.Children.OfType<FrameworkElement>())
59+
{
60+
if (item is TextBox tbox && tbox.PlaceholderText?.StartsWith("custom") == true)
61+
{
62+
tbox.PlaceholderText = $"custom: {appearance}";
63+
tbox.KeyboardAppearance = appearance;
64+
}
65+
else if (item is PasswordBox pbox && pbox.PlaceholderText?.StartsWith("custom") == true)
66+
{
67+
pbox.PlaceholderText = $"custom: {appearance}";
68+
pbox.KeyboardAppearance = appearance;
69+
}
70+
}
71+
#endif
72+
}
73+
}
74+
}

src/Uno.UI/UI/Xaml/Application.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ public partial class Application
4444
{
4545
private bool _initializationComplete = false;
4646
private readonly static IEventProvider _trace = Tracing.Get(TraceProvider.Id);
47-
private bool _themeSetExplicitly = false;
4847
private ApplicationTheme? _requestedTheme;
4948
private bool _systemThemeChangesObserved = false;
5049
private SpecializedResourceDictionary.ResourceKey _requestedThemeForResources;
@@ -159,10 +158,12 @@ private set
159158
_ => throw new InvalidOperationException("Application's RequestedTheme is invalid."),
160159
};
161160

161+
internal bool IsThemeSetExplicitly { get; private set; } = false;
162+
162163
internal void SetExplicitRequestedTheme(ApplicationTheme? explicitTheme)
163164
{
164165
// this flag makes sure the app will not respond to OS events
165-
_themeSetExplicitly = explicitTheme.HasValue;
166+
IsThemeSetExplicitly = explicitTheme.HasValue;
166167
var theme = explicitTheme ?? GetDefaultSystemTheme();
167168
SetRequestedTheme(theme);
168169
}
@@ -202,7 +203,7 @@ internal void SetExplicitRequestedTheme(ApplicationTheme? explicitTheme)
202203
public void OnSystemThemeChanged()
203204
{
204205
// if user overrides theme, don't apply system theme
205-
if (!_themeSetExplicitly)
206+
if (!IsThemeSetExplicitly)
206207
{
207208
var theme = GetDefaultSystemTheme();
208209
SetRequestedTheme(theme);

src/Uno.UI/UI/Xaml/Controls/PasswordBox/PasswordBox.iOSmacOS.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ namespace Windows.UI.Xaml.Controls
66
{
77
public partial class PasswordBox : TextBox
88
{
9+
// TODO: copy UpdateThemeBindings+UpdateKeyboardThemePartial impl when PasswordBox no longer inherits from TextBox
10+
911
partial void SetPasswordScope(bool shouldHideText)
1012
{
1113
SetSecureTextEntry(shouldHideText);

src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,5 +854,14 @@ public void Select(int start, int length)
854854
partial void SelectAllPartial();
855855

856856
internal override bool CanHaveChildren() => true;
857+
858+
internal override void UpdateThemeBindings()
859+
{
860+
base.UpdateThemeBindings();
861+
862+
UpdateKeyboardThemePartial();
863+
}
864+
865+
partial void UpdateKeyboardThemePartial();
857866
}
858867
}

src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.iOS.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using System.Text;
45
using Uno.UI;
56
using Windows.UI.Xaml.Data;
@@ -27,6 +28,7 @@ partial void InitializePropertiesPartial()
2728
OnTextAlignmentChanged(CreateInitialValueChangerEventArgs(TextAlignmentProperty, null, TextAlignment));
2829
OnReturnKeyTypeChanged(ReturnKeyType);
2930
OnKeyboardAppearanceChanged(KeyboardAppearance);
31+
UpdateKeyboardThemePartial();
3032
}
3133

3234
partial void OnFocusStateChangedPartial(FocusState focusState)
@@ -334,5 +336,35 @@ private void OnKeyboardAppearanceChanged(UIKeyboardAppearance newValue)
334336
}
335337

336338
#endregion
339+
340+
partial void UpdateKeyboardThemePartial()
341+
{
342+
if (_textBoxView == null) return;
343+
344+
// if KeyboardAppearance has been explicitly set, we leave it as is.
345+
if (KeyboardAppearance != UIKeyboardAppearance.Default) return;
346+
347+
ElementTheme? GetExplicitlySetAppTheme() => Application.Current.IsThemeSetExplicitly
348+
? Application.Current.ActualElementTheme as ElementTheme?
349+
: null;
350+
351+
// the appearance will be determined by the first parent/self that has a non-default RequestedTheme,
352+
// or the explicitly requested application theme.
353+
// note: the literal "Default" value is lost on the ActualTheme property, which is why we are not using it here.
354+
var theme = VisualTreeHelper.EnumerateAncestors(this).OfType<FrameworkElement>()
355+
.Prepend(this)
356+
.Select(x => x.RequestedTheme as ElementTheme?)
357+
.Append(GetExplicitlySetAppTheme())
358+
.FirstOrDefault(x => x != ElementTheme.Default);
359+
var appearance = theme switch
360+
{
361+
ElementTheme.Light => UIKeyboardAppearance.Light,
362+
ElementTheme.Dark => UIKeyboardAppearance.Dark,
363+
364+
_ => UIKeyboardAppearance.Default
365+
};
366+
367+
_textBoxView.KeyboardAppearance = appearance;
368+
}
337369
}
338370
}

src/Uno.UI/UI/Xaml/Media/VisualTreeHelper.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,15 @@ internal static UIElement SearchDownForLeaf(UIElement root, Predicate<UIElement>
510510
return root;
511511
}
512512

513+
internal static IEnumerable<DependencyObject> EnumerateAncestors(DependencyObject o)
514+
{
515+
while ((o as FrameworkElement)?.Parent is { } parent)
516+
{
517+
yield return parent;
518+
o = parent;
519+
}
520+
}
521+
513522
#region Helpers
514523
private static Func<IEnumerable<UIElement>, IEnumerable<UIElement>> Except(UIElement element)
515524
=> children => children.Except(element);

0 commit comments

Comments
 (0)