diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm
index d3b7b4ede6..23abf1d53f 100644
--- a/native/Avalonia.Native/src/OSX/AvnWindow.mm
+++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm
@@ -223,6 +223,19 @@
}
}
+// From chromium:
+//
+// > The delegate or the window class should implement this method so that
+// > -[NSWindow isZoomed] can be then determined by whether or not the current
+// > window frame is equal to the zoomed frame.
+//
+// If we don't implement this, then isZoomed always returns true for a non-
+// resizable window ¯\_(ツ)_/¯
+- (NSRect)windowWillUseStandardFrame:(NSWindow*)window
+ defaultFrame:(NSRect)newFrame {
+ return newFrame;
+}
+
-(BOOL)canBecomeKeyWindow
{
if(_canBecomeKeyWindow)
@@ -261,10 +274,6 @@
-(void) setEnabled:(bool)enable
{
_isEnabled = enable;
-
- [[self standardWindowButton:NSWindowCloseButton] setEnabled:enable];
- [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:enable];
- [[self standardWindowButton:NSWindowZoomButton] setEnabled:enable];
}
-(void)becomeKeyWindow
diff --git a/native/Avalonia.Native/src/OSX/PopupImpl.mm b/native/Avalonia.Native/src/OSX/PopupImpl.mm
index 9820a9f052..972d03d08c 100644
--- a/native/Avalonia.Native/src/OSX/PopupImpl.mm
+++ b/native/Avalonia.Native/src/OSX/PopupImpl.mm
@@ -29,7 +29,7 @@ private:
[Window setLevel:NSPopUpMenuWindowLevel];
}
protected:
- virtual NSWindowStyleMask GetStyle() override
+ virtual NSWindowStyleMask CalculateStyleMask() override
{
return NSWindowStyleMaskBorderless;
}
diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
index 4c2758f6c6..93decef136 100644
--- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
+++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
@@ -105,9 +105,8 @@ BEGIN_INTERFACE_MAP()
virtual void BringToFront ();
protected:
- virtual NSWindowStyleMask GetStyle();
-
- void UpdateStyle();
+ virtual NSWindowStyleMask CalculateStyleMask() = 0;
+ virtual void UpdateStyle();
private:
void CreateNSWindow (bool isDialog);
diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
index 038e9a048c..59102e15a6 100644
--- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
@@ -35,18 +35,14 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl,
lastSize = NSSize { 100, 100 };
lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
lastMinSize = NSSize { 0, 0 };
-
lastMenu = nullptr;
CreateNSWindow(usePanel);
[Window setContentView:StandardContainer];
- [Window setStyleMask:NSWindowStyleMaskBorderless];
[Window setBackingType:NSBackingStoreBuffered];
-
[Window setContentMinSize:lastMinSize];
[Window setContentMaxSize:lastMaxSize];
-
[Window setOpaque:false];
}
@@ -564,12 +560,8 @@ bool WindowBaseImpl::IsModal() {
return false;
}
-NSWindowStyleMask WindowBaseImpl::GetStyle() {
- return NSWindowStyleMaskBorderless;
-}
-
void WindowBaseImpl::UpdateStyle() {
- [Window setStyleMask:GetStyle()];
+ [Window setStyleMask:CalculateStyleMask()];
}
void WindowBaseImpl::CleanNSWindow() {
@@ -580,21 +572,12 @@ void WindowBaseImpl::CleanNSWindow() {
}
}
-void WindowBaseImpl::CreateNSWindow(bool isDialog) {
- if (isDialog) {
- if (![Window isKindOfClass:[AvnPanel class]]) {
- CleanNSWindow();
-
- Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
-
- [Window setHidesOnDeactivate:false];
- }
+void WindowBaseImpl::CreateNSWindow(bool usePanel) {
+ if (usePanel) {
+ Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless];
+ [Window setHidesOnDeactivate:false];
} else {
- if (![Window isKindOfClass:[AvnWindow class]]) {
- CleanNSWindow();
-
- Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
- }
+ Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless];
}
}
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h
index 3861aaf170..29bb659039 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.h
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.h
@@ -41,8 +41,6 @@ BEGIN_INTERFACE_MAP()
WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl);
- void HideOrShowTrafficLights ();
-
virtual HRESULT Show (bool activate, bool isDialog) override;
virtual HRESULT SetEnabled (bool enable) override;
@@ -100,9 +98,11 @@ BEGIN_INTERFACE_MAP()
bool CanBecomeKeyWindow ();
protected:
- virtual NSWindowStyleMask GetStyle() override;
+ virtual NSWindowStyleMask CalculateStyleMask() override;
+ void UpdateStyle () override;
private:
+ void ZOrderChildWindows();
void OnInitialiseNSWindow();
NSString *_lastTitle;
};
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm
index ce82f7d83f..cf1ee6943d 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm
@@ -30,19 +30,6 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase
OnInitialiseNSWindow();
}
-void WindowImpl::HideOrShowTrafficLights() {
- if (Window == nil) {
- return;
- }
-
- bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
- bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
-
- [[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights];
- [[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights];
- [[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights];
-}
-
void WindowImpl::OnInitialiseNSWindow(){
[GetWindowProtocol() setCanBecomeKeyWindow:true];
@@ -67,8 +54,6 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) {
WindowBaseImpl::Show(activate, isDialog);
GetWindowState(&_actualWindowState);
- HideOrShowTrafficLights();
-
return SetWindowState(_lastWindowState);
}
}
@@ -134,14 +119,19 @@ void WindowImpl::BringToFront()
}
[Window invalidateShadow];
+ ZOrderChildWindows();
+ }
+}
+
+void WindowImpl::ZOrderChildWindows()
+{
+ for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
+ {
+ auto window = (*iterator)->Window;
- for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
- {
- auto window = (*iterator)->Window;
-
- // #9565: Only bring window to front if it's on the currently active space
- if ([window isOnActiveSpace])
- (*iterator)->BringToFront();
+ // #9565: Only bring window to front if it's on the currently active space
+ if ([window isOnActiveSpace]) {
+ (*iterator)->BringToFront();
}
}
}
@@ -161,13 +151,15 @@ bool WindowImpl::CanBecomeKeyWindow()
void WindowImpl::StartStateTransition() {
_transitioningWindowState = true;
+ UpdateStyle();
}
void WindowImpl::EndStateTransition() {
_transitioningWindowState = false;
-
+ UpdateStyle();
+
// Ensure correct order of child windows after fullscreen transition.
- BringToFront();
+ ZOrderChildWindows();
}
SystemDecorations WindowImpl::Decorations() {
@@ -225,16 +217,12 @@ bool WindowImpl::IsZoomed() {
}
void WindowImpl::DoZoom() {
- switch (_decorations) {
- case SystemDecorationsNone:
- case SystemDecorationsBorderOnly:
- [Window setFrame:[Window screen].visibleFrame display:true];
- break;
-
-
- case SystemDecorationsFull:
- [Window performZoom:Window];
- break;
+ if (_decorations == SystemDecorationsNone ||
+ _decorations == SystemDecorationsBorderOnly ||
+ _canResize == false) {
+ [Window setFrame:[Window screen].visibleFrame display:true];
+ } else {
+ [Window performZoom:Window];
}
}
@@ -261,8 +249,6 @@ HRESULT WindowImpl::SetDecorations(SystemDecorations value) {
UpdateStyle();
- HideOrShowTrafficLights();
-
switch (_decorations) {
case SystemDecorationsNone:
[Window setHasShadow:NO];
@@ -419,9 +405,6 @@ HRESULT WindowImpl::SetExtendClientArea(bool enable) {
}
[GetWindowProtocol() setIsExtended:enable];
-
- HideOrShowTrafficLights();
-
UpdateStyle();
}
@@ -577,14 +560,16 @@ bool WindowImpl::IsOwned() {
return _parent != nullptr;
}
-NSWindowStyleMask WindowImpl::GetStyle() {
- unsigned long s = NSWindowStyleMaskBorderless;
+NSWindowStyleMask WindowImpl::CalculateStyleMask() {
+ // Use the current style mask and only clear the flags we're going to be modifying.
+ unsigned long s = [Window styleMask] &
+ ~(NSWindowStyleMaskFullSizeContentView |
+ NSWindowStyleMaskTitled |
+ NSWindowStyleMaskClosable |
+ NSWindowStyleMaskResizable |
+ NSWindowStyleMaskMiniaturizable |
+ NSWindowStyleMaskTexturedBackground);
- if(_actualWindowState == FullScreen)
- {
- s |= NSWindowStyleMaskFullScreen;
- }
-
switch (_decorations) {
case SystemDecorationsNone:
s = s | NSWindowStyleMaskFullSizeContentView;
@@ -597,7 +582,7 @@ NSWindowStyleMask WindowImpl::GetStyle() {
case SystemDecorationsFull:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
- if (_canResize && _isEnabled) {
+ if ((_canResize && _isEnabled) || _transitioningWindowState) {
s = s | NSWindowStyleMaskResizable;
}
break;
@@ -612,3 +597,25 @@ NSWindowStyleMask WindowImpl::GetStyle() {
}
return s;
}
+
+void WindowImpl::UpdateStyle() {
+ WindowBaseImpl::UpdateStyle();
+
+ if (Window == nil) {
+ return;
+ }
+
+ bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
+ bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
+
+ NSButton* closeButton = [Window standardWindowButton:NSWindowCloseButton];
+ NSButton* miniaturizeButton = [Window standardWindowButton:NSWindowMiniaturizeButton];
+ NSButton* zoomButton = [Window standardWindowButton:NSWindowZoomButton];
+
+ [closeButton setHidden:!hasTrafficLights];
+ [closeButton setEnabled:_isEnabled];
+ [miniaturizeButton setHidden:!hasTrafficLights];
+ [miniaturizeButton setEnabled:_isEnabled];
+ [zoomButton setHidden:!hasTrafficLights];
+ [zoomButton setEnabled:_isEnabled && _canResize];
+}
diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml
index 4e5a8463df..a6f463fdcb 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml
+++ b/samples/IntegrationTestApp/MainWindow.axaml
@@ -140,6 +140,7 @@
Maximized
FullScreen
+ Can Resize
diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs
index 3cd5350cce..087f25666b 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml.cs
+++ b/samples/IntegrationTestApp/MainWindow.axaml.cs
@@ -66,11 +66,13 @@ namespace IntegrationTestApp
var locationComboBox = this.GetControl("ShowWindowLocation");
var stateComboBox = this.GetControl("ShowWindowState");
var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null;
+ var canResizeCheckBox = this.GetControl("ShowWindowCanResize");
var owner = (Window)this.GetVisualRoot()!;
var window = new ShowWindowTest
{
WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex,
+ CanResize = canResizeCheckBox.IsChecked.Value,
};
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
diff --git a/samples/MobileSandbox/MainView.xaml b/samples/MobileSandbox/MainView.xaml
index 1eab13aa75..5d35ec3fec 100644
--- a/samples/MobileSandbox/MainView.xaml
+++ b/samples/MobileSandbox/MainView.xaml
@@ -5,8 +5,8 @@
x:DataType="mobileSandbox:MainView">
-
-
+
+
diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs
index c885a7768c..27dcfe8645 100644
--- a/src/Android/Avalonia.Android/AndroidInputMethod.cs
+++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs
@@ -5,8 +5,10 @@ using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.SkiaPlatform;
+using Avalonia.Controls.Presenters;
using Avalonia.Input;
using Avalonia.Input.TextInput;
+using Avalonia.Reactive;
namespace Avalonia.Android
{
@@ -32,7 +34,7 @@ namespace Avalonia.Android
ActionPrevious = 0x00000007,
}
- class AndroidInputMethod : ITextInputMethodImpl, IAndroidInputMethod
+ internal class AndroidInputMethod : ITextInputMethodImpl, IAndroidInputMethod
where TView : View, IInitEditorInfo
{
private readonly TView _host;
@@ -68,23 +70,10 @@ namespace Avalonia.Android
public void SetClient(ITextInputMethodClient client)
{
- if (_client != null)
- {
- _client.SurroundingTextChanged -= SurroundingTextChanged;
- }
-
- if(_inputConnection != null)
- {
- _inputConnection.ComposingText = null;
- _inputConnection.ComposingRegion = default;
- }
-
_client = client;
if (IsActive)
{
- _client.SurroundingTextChanged += SurroundingTextChanged;
-
_host.RequestFocus();
_imm.RestartInput(View);
@@ -101,24 +90,6 @@ namespace Avalonia.Android
}
}
- private void SurroundingTextChanged(object sender, EventArgs e)
- {
- if (IsActive && _inputConnection != null)
- {
- var surroundingText = Client.SurroundingText;
-
- _inputConnection.SurroundingText = surroundingText;
-
- _imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset);
-
- if (_inputConnection.ComposingText != null && !_inputConnection.IsCommiting && surroundingText.AnchorOffset == surroundingText.CursorOffset)
- {
- _inputConnection.CommitText(_inputConnection.ComposingText, 0);
- _inputConnection.SetSelection(surroundingText.AnchorOffset, surroundingText.CursorOffset);
- }
- }
- }
-
public void SetCursorRect(Rect rect)
{
@@ -157,17 +128,20 @@ namespace Avalonia.Android
TextInputReturnKeyType.Search => (ImeFlags)CustomImeFlags.ActionSearch,
TextInputReturnKeyType.Next => (ImeFlags)CustomImeFlags.ActionNext,
TextInputReturnKeyType.Previous => (ImeFlags)CustomImeFlags.ActionPrevious,
- _ => (ImeFlags)CustomImeFlags.ActionDone
+ TextInputReturnKeyType.Done => (ImeFlags)CustomImeFlags.ActionDone,
+ _ => options.Multiline ? ImeFlags.NoEnterAction : (ImeFlags)CustomImeFlags.ActionDone
};
outAttrs.ImeOptions |= ImeFlags.NoFullscreen | ImeFlags.NoExtractUi;
+ _client.TextEditable = _inputConnection.InputEditable;
+
return _inputConnection;
});
}
}
- public readonly record struct ComposingRegion
+ internal readonly record struct ComposingRegion
{
private readonly int _start = -1;
private readonly int _end = -1;
diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj
index 66557418dd..2533016e9f 100644
--- a/src/Android/Avalonia.Android/Avalonia.Android.csproj
+++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj
@@ -5,6 +5,7 @@
true
true
portable
+ Avalonia.Android.Internal
diff --git a/src/Android/Avalonia.Android/InputEditable.cs b/src/Android/Avalonia.Android/InputEditable.cs
new file mode 100644
index 0000000000..c5b68d2652
--- /dev/null
+++ b/src/Android/Avalonia.Android/InputEditable.cs
@@ -0,0 +1,127 @@
+using System;
+using Android.Runtime;
+using Android.Text;
+using Android.Views;
+using Android.Views.InputMethods;
+using Avalonia.Android.Platform.SkiaPlatform;
+using Avalonia.Controls.Presenters;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Input.TextInput;
+using Java.Lang;
+using static System.Net.Mime.MediaTypeNames;
+
+namespace Avalonia.Android
+{
+ internal class InputEditable : SpannableStringBuilder, ITextEditable
+ {
+ private readonly TopLevelImpl _topLevel;
+ private readonly IAndroidInputMethod _inputMethod;
+ private readonly AvaloniaInputConnection _avaloniaInputConnection;
+ private int _currentBatchLevel;
+ private string _previousText;
+ private int _previousSelectionStart;
+ private int _previousSelectionEnd;
+
+ public event EventHandler TextChanged;
+ public event EventHandler SelectionChanged;
+ public event EventHandler CompositionChanged;
+
+ public InputEditable(TopLevelImpl topLevel, IAndroidInputMethod inputMethod, AvaloniaInputConnection avaloniaInputConnection)
+ {
+ _topLevel = topLevel;
+ _inputMethod = inputMethod;
+ _avaloniaInputConnection = avaloniaInputConnection;
+ }
+
+ public InputEditable(ICharSequence text) : base(text)
+ {
+ }
+
+ public InputEditable(string text) : base(text)
+ {
+ }
+
+ public InputEditable(ICharSequence text, int start, int end) : base(text, start, end)
+ {
+ }
+
+ public InputEditable(string text, int start, int end) : base(text, start, end)
+ {
+ }
+
+ protected InputEditable(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
+ {
+ }
+
+ public int SelectionStart
+ {
+ get => Selection.GetSelectionStart(this); set
+ {
+ var end = SelectionEnd < 0 ? 0 : SelectionEnd;
+ _avaloniaInputConnection.SetSelection(value, end);
+ _inputMethod.IMM.UpdateSelection(_topLevel.View, value, end, value, end);
+ }
+ }
+ public int SelectionEnd
+ {
+ get => Selection.GetSelectionEnd(this); set
+ {
+ var start = SelectionStart < 0 ? 0 : SelectionStart;
+ _avaloniaInputConnection.SetSelection(start, value);
+ _inputMethod.IMM.UpdateSelection(_topLevel.View, start, value, start, value);
+ }
+ }
+
+ public string? Text
+ {
+ get => ToString(); set
+ {
+ if (Text != value)
+ {
+ Clear();
+ Insert(0, value ?? "");
+ }
+ }
+ }
+
+ public int CompositionStart => BaseInputConnection.GetComposingSpanStart(this);
+
+ public int CompositionEnd => BaseInputConnection.GetComposingSpanEnd(this);
+
+ public void BeginBatchEdit()
+ {
+ _currentBatchLevel++;
+
+ if (_currentBatchLevel == 1)
+ {
+ _previousText = ToString();
+ _previousSelectionStart = SelectionStart;
+ _previousSelectionEnd = SelectionEnd;
+ }
+ }
+
+ public void EndBatchEdit()
+ {
+ if (_currentBatchLevel == 1)
+ {
+ if(_previousText != Text)
+ {
+ TextChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ if (_previousSelectionStart != SelectionStart || _previousSelectionEnd != SelectionEnd)
+ {
+ SelectionChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ _currentBatchLevel--;
+ }
+
+ public void RaiseCompositionChanged()
+ {
+ CompositionChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+}
diff --git a/src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs b/src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs
index 726ccdbbdd..ab84801e57 100644
--- a/src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs
+++ b/src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs
@@ -5,7 +5,7 @@ using Avalonia.Input;
namespace Avalonia.Android.Platform.Input
{
- public class AndroidKeyboardDevice : KeyboardDevice, IKeyboardDevice {
+ internal class AndroidKeyboardDevice : KeyboardDevice, IKeyboardDevice {
private static readonly Dictionary KeyDic = new Dictionary
{
// { Keycode.Cancel?, Key.Cancel },
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
index f205458f0e..47297a4f76 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
@@ -10,7 +10,7 @@ using Avalonia.Platform;
namespace Avalonia.Android
{
- public abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformNativeSurfaceHandle
+ internal abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformNativeSurfaceHandle
{
bool _invalidateQueued;
readonly object _lock = new object();
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index 693a26f3bd..e511ed9a8b 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -28,6 +28,7 @@ using Math = System.Math;
using AndroidRect = Android.Graphics.Rect;
using Window = Android.Views.Window;
using Android.Graphics.Drawables;
+using Java.Util;
namespace Avalonia.Android.Platform.SkiaPlatform
{
@@ -410,159 +411,73 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
private readonly TopLevelImpl _topLevel;
private readonly IAndroidInputMethod _inputMethod;
+ private readonly InputEditable _editable;
public AvaloniaInputConnection(TopLevelImpl topLevel, IAndroidInputMethod inputMethod) : base(inputMethod.View, true)
{
_topLevel = topLevel;
_inputMethod = inputMethod;
+ _editable = new InputEditable(_topLevel, _inputMethod, this);
}
- public TextInputMethodSurroundingText SurroundingText { get; set; }
+ public override IEditable Editable => _editable;
- public string ComposingText { get; internal set; }
-
- public ComposingRegion? ComposingRegion { get; internal set; }
-
- public bool IsComposing => !string.IsNullOrEmpty(ComposingText);
- public bool IsCommiting { get; private set; }
+ internal InputEditable InputEditable => _editable;
public override bool SetComposingRegion(int start, int end)
{
- //System.Diagnostics.Debug.WriteLine($"Composing Region: [{start}|{end}] {SurroundingText.Text?.Substring(start, end - start)}");
+ var ret = base.SetComposingRegion(start, end);
- ComposingRegion = new ComposingRegion(start, end);
+ InputEditable.RaiseCompositionChanged();
- return base.SetComposingRegion(start, end);
+ return ret;
}
public override bool SetComposingText(ICharSequence text, int newCursorPosition)
{
var composingText = text.ToString();
- ComposingText = composingText;
-
- _inputMethod.Client?.SetPreeditText(ComposingText);
-
- return base.SetComposingText(text, newCursorPosition);
- }
-
- public override bool FinishComposingText()
- {
- if (!string.IsNullOrEmpty(ComposingText))
+ if (string.IsNullOrEmpty(composingText))
{
- CommitText(ComposingText, ComposingText.Length);
+ return CommitText(text, newCursorPosition);
}
else
{
- ComposingRegion = new ComposingRegion(SurroundingText.CursorOffset, SurroundingText.CursorOffset);
- }
-
- return base.FinishComposingText();
- }
-
- public override ICharSequence GetTextBeforeCursorFormatted(int length, [GeneratedEnum] GetTextFlags flags)
- {
- if (!string.IsNullOrEmpty(SurroundingText.Text) && length > 0)
- {
- var start = System.Math.Max(SurroundingText.CursorOffset - length, 0);
+ var ret = base.SetComposingText(text, newCursorPosition);
- var end = System.Math.Min(start + length - 1, SurroundingText.CursorOffset);
+ InputEditable.RaiseCompositionChanged();
- var text = SurroundingText.Text.Substring(start, end - start);
-
- //System.Diagnostics.Debug.WriteLine($"Text Before: {text}");
-
- return new Java.Lang.String(text);
+ return ret;
}
-
- return null;
}
- public override ICharSequence GetTextAfterCursorFormatted(int length, [GeneratedEnum] GetTextFlags flags)
+ public override bool BeginBatchEdit()
{
- if (!string.IsNullOrEmpty(SurroundingText.Text))
- {
- var start = SurroundingText.CursorOffset;
-
- var end = System.Math.Min(start + length, SurroundingText.Text.Length);
-
- var text = SurroundingText.Text.Substring(start, end - start);
+ _editable.BeginBatchEdit();
- //System.Diagnostics.Debug.WriteLine($"Text After: {text}");
-
- return new Java.Lang.String(text);
- }
-
- return null;
+ return base.BeginBatchEdit();
}
- public override bool CommitText(ICharSequence text, int newCursorPosition)
+ public override bool EndBatchEdit()
{
- IsCommiting = true;
- var committedText = text.ToString();
-
- _inputMethod.Client.SetPreeditText(null);
+ var ret = base.EndBatchEdit();
+ _editable.EndBatchEdit();
- int? start, end;
-
- if(SurroundingText.CursorOffset != SurroundingText.AnchorOffset)
- {
- start = Math.Min(SurroundingText.CursorOffset, SurroundingText.AnchorOffset);
- end = Math.Max(SurroundingText.CursorOffset, SurroundingText.AnchorOffset);
- }
- else if (ComposingRegion != null)
- {
- start = ComposingRegion?.Start;
- end = ComposingRegion?.End;
-
- ComposingRegion = null;
- }
- else
- {
- start = end = _inputMethod.Client.SurroundingText.CursorOffset;
- }
-
- _inputMethod.Client.SelectInSurroundingText((int)start, (int)end);
-
- var time = DateTime.Now.TimeOfDay;
-
- var rawTextEvent = new RawTextInputEventArgs(KeyboardDevice.Instance, (ulong)time.Ticks, _topLevel.InputRoot, committedText);
-
- _topLevel.Input(rawTextEvent);
-
- ComposingText = null;
-
- ComposingRegion = new ComposingRegion(newCursorPosition, newCursorPosition);
-
- return base.CommitText(text, newCursorPosition);
+ return ret;
}
- public override bool DeleteSurroundingText(int beforeLength, int afterLength)
+ public override bool FinishComposingText()
{
- var surroundingText = _inputMethod.Client.SurroundingText;
-
- var selectionStart = surroundingText.CursorOffset;
-
- _inputMethod.Client.SelectInSurroundingText(selectionStart - beforeLength, selectionStart + afterLength);
-
- _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
-
- surroundingText = _inputMethod.Client.SurroundingText;
-
- selectionStart = surroundingText.CursorOffset;
-
- ComposingRegion = new ComposingRegion(selectionStart, selectionStart);
-
- return base.DeleteSurroundingText(beforeLength, afterLength);
+ var ret = base.FinishComposingText();
+ InputEditable.RaiseCompositionChanged();
+ return ret;
}
- public override bool SetSelection(int start, int end)
+ public override bool CommitText(ICharSequence text, int newCursorPosition)
{
- _inputMethod.Client.SelectInSurroundingText(start, end);
-
- ComposingRegion = new ComposingRegion(start, end);
-
- return base.SetSelection(start, end);
+ var ret = base.CommitText(text, newCursorPosition);
+ InputEditable.RaiseCompositionChanged();
+ return ret;
}
public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode)
diff --git a/src/Android/Avalonia.Android/PlatformIconLoader.cs b/src/Android/Avalonia.Android/PlatformIconLoader.cs
index 88677a9375..f557685dd2 100644
--- a/src/Android/Avalonia.Android/PlatformIconLoader.cs
+++ b/src/Android/Avalonia.Android/PlatformIconLoader.cs
@@ -3,7 +3,7 @@ using Avalonia.Platform;
namespace Avalonia.Android
{
- class PlatformIconLoader : IPlatformIconLoader
+ internal class PlatformIconLoader : IPlatformIconLoader
{
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
{
@@ -29,7 +29,7 @@ namespace Avalonia.Android
}
// Stores the icon created as a stream to support saving even though an icon is never shown
- public class FakeIcon : IWindowIconImpl
+ internal class FakeIcon : IWindowIconImpl
{
private Stream stream = new MemoryStream();
diff --git a/src/Android/Avalonia.Android/Stubs.cs b/src/Android/Avalonia.Android/Stubs.cs
index f36c01dbc8..05638cdf88 100644
--- a/src/Android/Avalonia.Android/Stubs.cs
+++ b/src/Android/Avalonia.Android/Stubs.cs
@@ -4,7 +4,7 @@ using Avalonia.Platform;
namespace Avalonia.Android
{
- class WindowingPlatformStub : IWindowingPlatform
+ internal class WindowingPlatformStub : IWindowingPlatform
{
public IWindowImpl CreateWindow() => throw new NotSupportedException();
@@ -13,7 +13,7 @@ namespace Avalonia.Android
public ITrayIconImpl CreateTrayIcon() => null;
}
- class PlatformIconLoaderStub : IPlatformIconLoader
+ internal class PlatformIconLoaderStub : IPlatformIconLoader
{
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
{
@@ -38,7 +38,7 @@ namespace Avalonia.Android
}
}
- public class IconStub : IWindowIconImpl
+ internal class IconStub : IWindowIconImpl
{
private readonly MemoryStream _ms;
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 74dc55355b..2c9efc7767 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -118,7 +118,7 @@ namespace Avalonia
{
_ = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
- _values.ClearLocalValue(property);
+ _values.ClearValue(property);
}
///
@@ -152,7 +152,7 @@ namespace Avalonia
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
- _values.ClearLocalValue(property);
+ _values.ClearValue(property);
}
///
@@ -329,7 +329,7 @@ namespace Avalonia
if (value is UnsetValueType)
{
if (priority == BindingPriority.LocalValue)
- _values.ClearLocalValue(property);
+ _values.ClearValue(property);
}
else if (value is not DoNothingType)
{
@@ -355,6 +355,57 @@ namespace Avalonia
SetDirectValueUnchecked(property, value);
}
+ ///
+ /// Sets the value of a dependency property without changing its value source.
+ ///
+ /// The property.
+ /// The value.
+ ///
+ /// This method is used by a component that programmatically sets the value of one of its
+ /// own properties without disabling an application's declared use of the property. The
+ /// method changes the effective value of the property, but existing data bindings and
+ /// styles will continue to work.
+ ///
+ /// The new value will have the property's current , even if
+ /// that priority is or
+ /// .
+ ///
+ public void SetCurrentValue(AvaloniaProperty property, object? value) =>
+ property.RouteSetCurrentValue(this, value);
+
+ ///
+ /// Sets the value of a dependency property without changing its value source.
+ ///
+ /// The type of the property.
+ /// The property.
+ /// The value.
+ ///
+ /// This method is used by a component that programmatically sets the value of one of its
+ /// own properties without disabling an application's declared use of the property. The
+ /// method changes the effective value of the property, but existing data bindings and
+ /// styles will continue to work.
+ ///
+ /// The new value will have the property's current , even if
+ /// that priority is or
+ /// .
+ ///
+ public void SetCurrentValue(StyledProperty property, T value)
+ {
+ _ = property ?? throw new ArgumentNullException(nameof(property));
+ VerifyAccess();
+
+ LogPropertySet(property, value, BindingPriority.LocalValue);
+
+ if (value is UnsetValueType)
+ {
+ _values.ClearValue(property);
+ }
+ else if (value is not DoNothingType)
+ {
+ _values.SetCurrentValue(property, value);
+ }
+ }
+
///
/// Binds a to an observable.
///
@@ -547,7 +598,8 @@ namespace Avalonia
property,
GetValue(property),
BindingPriority.LocalValue,
- null);
+ null,
+ false);
}
return _values.GetDiagnostic(property);
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index 5db4d81f03..45ab293a89 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -225,13 +225,8 @@ namespace Avalonia
/// The default value of the property.
/// Whether the property inherits its value.
/// The default binding mode for the property.
- /// A value validation callback.
+ /// A value validation callback.
/// A value coercion callback.
- ///
- /// A method that gets called before and after the property starts being notified on an
- /// object; the bool argument will be true before and false afterwards. This callback is
- /// intended to support IsDataContextChanging.
- ///
/// A
public static StyledProperty Register(
string name,
@@ -239,8 +234,40 @@ namespace Avalonia
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay,
Func? validate = null,
- Func? coerce = null,
- Action? notifying = null)
+ Func? coerce = null)
+ where TOwner : AvaloniaObject
+ {
+ _ = name ?? throw new ArgumentNullException(nameof(name));
+
+ var metadata = new StyledPropertyMetadata(
+ defaultValue,
+ defaultBindingMode: defaultBindingMode,
+ coerce: coerce);
+
+ var result = new StyledProperty(
+ name,
+ typeof(TOwner),
+ metadata,
+ inherits,
+ validate);
+ AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result);
+ return result;
+ }
+
+ ///
+ ///
+ /// A method that gets called before and after the property starts being notified on an
+ /// object; the bool argument will be true before and false afterwards. This callback is
+ /// intended to support IsDataContextChanging.
+ ///
+ internal static StyledProperty Register(
+ string name,
+ TValue defaultValue,
+ bool inherits,
+ BindingMode defaultBindingMode,
+ Func? validate,
+ Func? coerce,
+ Action? notifying)
where TOwner : AvaloniaObject
{
_ = name ?? throw new ArgumentNullException(nameof(name));
@@ -496,6 +523,13 @@ namespace Avalonia
object? value,
BindingPriority priority);
+ ///
+ /// Routes an untyped SetCurrentValue call to a typed call.
+ ///
+ /// The object instance.
+ /// The value.
+ internal abstract void RouteSetCurrentValue(AvaloniaObject o, object? value);
+
///
/// Routes an untyped Bind call to a typed call.
///
diff --git a/src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs b/src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs
index 4189fd5234..0b3e62f1cc 100644
--- a/src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs
+++ b/src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs
@@ -3,28 +3,23 @@ using Avalonia.Data;
namespace Avalonia.Diagnostics
{
///
- /// Holds diagnostic-related information about the value of a
- /// on a .
+ /// Holds diagnostic-related information about the value of an
+ /// on an .
///
public class AvaloniaPropertyValue
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The property.
- /// The current property value.
- /// The priority of the current value.
- /// A diagnostic string.
- public AvaloniaPropertyValue(
+ internal AvaloniaPropertyValue(
AvaloniaProperty property,
object? value,
BindingPriority priority,
- string? diagnostic)
+ string? diagnostic,
+ bool isOverriddenCurrentValue)
{
Property = property;
Value = value;
Priority = priority;
Diagnostic = diagnostic;
+ IsOverriddenCurrentValue = isOverriddenCurrentValue;
}
///
@@ -46,5 +41,11 @@ namespace Avalonia.Diagnostics
/// Gets a diagnostic string.
///
public string? Diagnostic { get; }
+
+ ///
+ /// Gets a value indicating whether the was overridden by a call to
+ /// .
+ ///
+ public bool IsOverriddenCurrentValue { get; }
}
}
diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs
index 9ee1eee0fa..94dfaaab01 100644
--- a/src/Avalonia.Base/DirectPropertyBase.cs
+++ b/src/Avalonia.Base/DirectPropertyBase.cs
@@ -152,6 +152,11 @@ namespace Avalonia
return null;
}
+ internal override void RouteSetCurrentValue(AvaloniaObject o, object? value)
+ {
+ RouteSetValue(o, value, BindingPriority.LocalValue);
+ }
+
///
/// Routes an untyped Bind call to a typed call.
///
diff --git a/src/Avalonia.Base/Input/TextInput/ITextEditable.cs b/src/Avalonia.Base/Input/TextInput/ITextEditable.cs
new file mode 100644
index 0000000000..f9f5ec8d40
--- /dev/null
+++ b/src/Avalonia.Base/Input/TextInput/ITextEditable.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Metadata;
+
+namespace Avalonia.Input.TextInput
+{
+ [NotClientImplementable]
+ public interface ITextEditable
+ {
+ event EventHandler TextChanged;
+ event EventHandler SelectionChanged;
+ event EventHandler CompositionChanged;
+ int SelectionStart { get; set; }
+ int SelectionEnd { get; set; }
+ int CompositionStart { get; }
+ int CompositionEnd { get; }
+
+ string? Text { get; set; }
+ }
+}
diff --git a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
index 531cf3c704..2cdcd33626 100644
--- a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
+++ b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
@@ -1,4 +1,5 @@
using System;
+using Avalonia.Media.TextFormatting;
using Avalonia.VisualTree;
namespace Avalonia.Input.TextInput
@@ -30,6 +31,11 @@ namespace Avalonia.Input.TextInput
///
void SetPreeditText(string? text);
+ ///
+ /// Sets the current composing region. This doesn't remove the composing text from the commited text.
+ ///
+ void SetComposingRegion(TextRange? region);
+
///
/// Indicates if text input client is capable of providing the text around the cursor
///
@@ -43,6 +49,11 @@ namespace Avalonia.Input.TextInput
///
event EventHandler? SurroundingTextChanged;
+ ///
+ /// Gets or sets a platform editable. Text and selection changes made in the editable are forwarded to the IM client.
+ ///
+ ITextEditable? TextEditable { get; set; }
+
void SelectInSurroundingText(int start, int end);
}
diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs
index 04d3c805c2..78f0ad46b7 100644
--- a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs
+++ b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs
@@ -29,6 +29,12 @@ namespace Avalonia.PropertyStore
///
public BindingPriority BasePriority { get; protected set; }
+ ///
+ /// Gets a value indicating whether the was overridden by a call to
+ /// .
+ ///
+ public bool IsOverridenCurrentValue { get; set; }
+
///
/// Begins a reevaluation pass on the effective value.
///
diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
index 3e20dcce56..c469034f9b 100644
--- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
+++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
@@ -19,13 +19,16 @@ namespace Avalonia.PropertyStore
private T? _baseValue;
private UncommonFields? _uncommon;
- public EffectiveValue(AvaloniaObject owner, StyledProperty property)
+ public EffectiveValue(
+ AvaloniaObject owner,
+ StyledProperty property,
+ EffectiveValue? inherited)
{
Priority = BindingPriority.Unset;
BasePriority = BindingPriority.Unset;
_metadata = property.GetMetadata(owner.GetType());
- var value = _metadata.DefaultValue;
+ var value = inherited is null ? _metadata.DefaultValue : inherited.Value;
if (property.HasCoercion && _metadata.CoerceValue is { } coerce)
{
@@ -57,7 +60,7 @@ namespace Avalonia.PropertyStore
Debug.Assert(priority != BindingPriority.LocalValue);
UpdateValueEntry(value, priority);
- SetAndRaiseCore(owner, (StyledProperty)value.Property, GetValue(value), priority);
+ SetAndRaiseCore(owner, (StyledProperty)value.Property, GetValue(value), priority, false);
}
public void SetLocalValueAndRaise(
@@ -65,7 +68,16 @@ namespace Avalonia.PropertyStore
StyledProperty property,
T value)
{
- SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue);
+ SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue, false);
+ }
+
+ public void SetCurrentValueAndRaise(
+ ValueStore owner,
+ StyledProperty property,
+ T value)
+ {
+ IsOverridenCurrentValue = true;
+ SetAndRaiseCore(owner, property, value, Priority, true);
}
public bool TryGetBaseValue([MaybeNullWhen(false)] out T value)
@@ -98,7 +110,7 @@ namespace Avalonia.PropertyStore
Debug.Assert(Priority != BindingPriority.Animation);
Debug.Assert(BasePriority != BindingPriority.Unset);
UpdateValueEntry(null, BindingPriority.Animation);
- SetAndRaiseCore(owner, (StyledProperty)property, _baseValue!, BasePriority);
+ SetAndRaiseCore(owner, (StyledProperty)property, _baseValue!, BasePriority, false);
}
public override void CoerceValue(ValueStore owner, AvaloniaProperty property)
@@ -158,15 +170,16 @@ namespace Avalonia.PropertyStore
ValueStore owner,
StyledProperty property,
T value,
- BindingPriority priority)
+ BindingPriority priority,
+ bool isOverriddenCurrentValue)
{
- Debug.Assert(priority < BindingPriority.Inherited);
-
var oldValue = Value;
var valueChanged = false;
var baseValueChanged = false;
var v = value;
+ IsOverridenCurrentValue = isOverriddenCurrentValue;
+
if (_uncommon?._coerce is { } coerce)
v = coerce(owner.Owner, value);
@@ -209,7 +222,6 @@ namespace Avalonia.PropertyStore
T baseValue,
BindingPriority basePriority)
{
- Debug.Assert(priority < BindingPriority.Inherited);
Debug.Assert(basePriority > BindingPriority.Animation);
Debug.Assert(priority <= basePriority);
@@ -225,7 +237,7 @@ namespace Avalonia.PropertyStore
bv = coerce(owner.Owner, baseValue);
}
- if (priority != BindingPriority.Unset && !EqualityComparer.Default.Equals(Value, v))
+ if (!EqualityComparer.Default.Equals(Value, v))
{
Value = v;
valueChanged = true;
@@ -233,9 +245,7 @@ namespace Avalonia.PropertyStore
_uncommon._uncoercedValue = value;
}
- if (priority != BindingPriority.Unset &&
- (BasePriority == BindingPriority.Unset ||
- !EqualityComparer.Default.Equals(_baseValue, bv)))
+ if (!EqualityComparer.Default.Equals(_baseValue, bv))
{
_baseValue = v;
baseValueChanged = true;
diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs
index a758360545..ec6ed392c1 100644
--- a/src/Avalonia.Base/PropertyStore/ValueStore.cs
+++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs
@@ -7,7 +7,6 @@ using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Styling;
using Avalonia.Utilities;
-using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
namespace Avalonia.PropertyStore
{
@@ -156,11 +155,12 @@ namespace Avalonia.PropertyStore
return observer;
}
- public void ClearLocalValue(AvaloniaProperty property)
+ public void ClearValue(AvaloniaProperty property)
{
if (TryGetEffectiveValue(property, out var effective) &&
- effective.Priority == BindingPriority.LocalValue)
+ (effective.Priority == BindingPriority.LocalValue || effective.IsOverridenCurrentValue))
{
+ effective.IsOverridenCurrentValue = false;
ReevaluateEffectiveValue(property, effective, ignoreLocalValue: true);
}
}
@@ -184,7 +184,7 @@ namespace Avalonia.PropertyStore
}
else
{
- var effectiveValue = new EffectiveValue(Owner, property);
+ var effectiveValue = CreateEffectiveValue(property);
AddEffectiveValue(property, effectiveValue);
effectiveValue.SetAndRaise(this, result, priority);
}
@@ -200,7 +200,7 @@ namespace Avalonia.PropertyStore
}
else
{
- var effectiveValue = new EffectiveValue(Owner, property);
+ var effectiveValue = CreateEffectiveValue(property);
AddEffectiveValue(property, effectiveValue);
effectiveValue.SetLocalValueAndRaise(this, property, value);
}
@@ -209,6 +209,20 @@ namespace Avalonia.PropertyStore
}
}
+ public void SetCurrentValue(StyledProperty property, T value)
+ {
+ if (TryGetEffectiveValue(property, out var v))
+ {
+ ((EffectiveValue)v).SetCurrentValueAndRaise(this, property, value);
+ }
+ else
+ {
+ var effectiveValue = CreateEffectiveValue(property);
+ AddEffectiveValue(property, effectiveValue);
+ effectiveValue.SetCurrentValueAndRaise(this, property, value);
+ }
+ }
+
public object? GetValue(AvaloniaProperty property)
{
if (_effectiveValues.TryGetValue(property, out var v))
@@ -235,12 +249,7 @@ namespace Avalonia.PropertyStore
return false;
}
- public bool IsSet(AvaloniaProperty property)
- {
- if (_effectiveValues.TryGetValue(property, out var v))
- return v.Priority < BindingPriority.Inherited;
- return false;
- }
+ public bool IsSet(AvaloniaProperty property) => _effectiveValues.TryGetValue(property, out _);
public void CoerceValue(AvaloniaProperty property)
{
@@ -278,6 +287,16 @@ namespace Avalonia.PropertyStore
return false;
}
+ public EffectiveValue CreateEffectiveValue(StyledProperty property)
+ {
+ EffectiveValue? inherited = null;
+
+ if (property.Inherits && TryGetInheritedValue(property, out var v))
+ inherited = (EffectiveValue)v;
+
+ return new EffectiveValue(Owner, property, inherited);
+ }
+
public void SetInheritanceParent(AvaloniaObject? newParent)
{
var values = AvaloniaPropertyDictionaryPool.Get();
@@ -490,7 +509,7 @@ namespace Avalonia.PropertyStore
if (existing == observer)
{
_localValueBindings?.Remove(property.Id);
- ClearLocalValue(property);
+ ClearValue(property);
}
}
}
@@ -616,11 +635,13 @@ namespace Avalonia.PropertyStore
{
object? value;
BindingPriority priority;
+ bool overridden = false;
if (_effectiveValues.TryGetValue(property, out var v))
{
value = v.Value;
priority = v.Priority;
+ overridden = v.IsOverridenCurrentValue;
}
else if (property.Inherits && TryGetInheritedValue(property, out v))
{
@@ -637,7 +658,8 @@ namespace Avalonia.PropertyStore
property,
value,
priority,
- null);
+ null,
+ overridden);
}
private int InsertFrame(ValueFrame frame)
@@ -787,7 +809,7 @@ namespace Avalonia.PropertyStore
// - The value is a non-animation value and its priority is higher than the current
// effective value's base priority
var isRelevantPriority = current is null ||
- priority < current.Priority ||
+ (priority < current.Priority && priority < current.BasePriority) ||
(priority > BindingPriority.Animation && priority < current.BasePriority);
if (foundEntry && isRelevantPriority && entry!.HasValue)
diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs
index 5bf022cd51..2cdb973174 100644
--- a/src/Avalonia.Base/StyledElement.cs
+++ b/src/Avalonia.Base/StyledElement.cs
@@ -41,7 +41,11 @@ namespace Avalonia
public static readonly StyledProperty