diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index c2a9faf70c..7782165263 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -267,7 +267,8 @@ AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase { - virtual HRESULT ShowDialog (IAvnWindow* parent) = 0; + virtual HRESULT SetEnabled (bool enable) = 0; + virtual HRESULT SetParent (IAvnWindow* parent) = 0; virtual HRESULT SetCanResize(bool value) = 0; virtual HRESULT SetDecorations(SystemDecorations value) = 0; virtual HRESULT SetTitle (void* utf8Title) = 0; @@ -309,6 +310,8 @@ AVNCOM(IAvnWindowEvents, 06) : IAvnWindowBaseEvents virtual bool Closing () = 0; virtual void WindowStateChanged (AvnWindowState state) = 0; + + virtual void GotInputWhenDisabled () = 0; }; AVNCOM(IAvnMacOptions, 07) : IUnknown diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h index 163db36800..ca60914526 100644 --- a/native/Avalonia.Native/src/OSX/window.h +++ b/native/Avalonia.Native/src/OSX/window.h @@ -19,8 +19,7 @@ class WindowBaseImpl; -(void) pollModalSession: (NSModalSession _Nonnull) session; -(void) restoreParentWindow; -(bool) shouldTryToHandleEvents; --(bool) isModal; --(void) setModal: (bool) isModal; +-(void) setEnabled: (bool) enable; -(void) showAppMenuOnly; -(void) showWindowMenuWithAppMenu; -(void) applyMenu:(NSMenu* _Nullable)menu; diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 06b0c50456..fed2176580 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -497,12 +497,7 @@ private: virtual HRESULT Show () override { @autoreleasepool - { - if([Window parentWindow] != nil) - [[Window parentWindow] removeChildWindow:Window]; - - [Window setModal:FALSE]; - + { WindowBaseImpl::Show(); HideOrShowTrafficLights(); @@ -511,7 +506,16 @@ private: } } - virtual HRESULT ShowDialog (IAvnWindow* parent) override + virtual HRESULT SetEnabled (bool enable) override + { + @autoreleasepool + { + [Window setEnabled:enable]; + return S_OK; + } + } + + virtual HRESULT SetParent (IAvnWindow* parent) override { @autoreleasepool { @@ -522,12 +526,9 @@ private: if(cparent == nullptr) return E_INVALIDARG; - [Window setModal:TRUE]; - [cparent->Window addChildWindow:Window ordered:NSWindowAbove]; - WindowBaseImpl::Show(); - HideOrShowTrafficLights(); + UpdateStyle(); return S_OK; } @@ -883,15 +884,15 @@ protected: switch (_decorations) { case SystemDecorationsNone: - s = s | NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskMiniaturizable; + s = s | NSWindowStyleMaskFullSizeContentView; break; case SystemDecorationsBorderOnly: - s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskMiniaturizable; + s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView; break; case SystemDecorationsFull: - s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskBorderless; + s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskBorderless; if(_canResize) { @@ -900,6 +901,10 @@ protected: break; } + if([Window parentWindow] == nullptr) + { + s |= NSWindowStyleMaskMiniaturizable; + } return s; } }; @@ -1081,15 +1086,28 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent - (bool) ignoreUserInput { auto parentWindow = objc_cast([self window]); + if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents]) + { + auto window = dynamic_cast(_parent.getRaw()); + + if(window != nullptr) + { + window->WindowEvents->GotInputWhenDisabled(); + } + return TRUE; + } + return FALSE; } - (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type { if([self ignoreUserInput]) + { return; + } [self becomeFirstResponder]; auto localPoint = [self convertPoint:[event locationInWindow] toView:self]; @@ -1234,7 +1252,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent - (void) keyboardEvent: (NSEvent *) event withType: (AvnRawKeyEventType)type { if([self ignoreUserInput]) + { return; + } + auto key = s_KeyMap[[event keyCode]]; auto timestamp = [event timestamp] * 1000; @@ -1416,7 +1437,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent ComPtr _parent; bool _canBecomeKeyAndMain; bool _closed; - bool _isModal; + bool _isEnabled; AvnMenu* _menu; double _lastScaling; } @@ -1538,6 +1559,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _parent = parent; [self setDelegate:self]; _closed = false; + _isEnabled = true; _lastScaling = [self backingScaleFactor]; [self setOpaque:NO]; @@ -1604,28 +1626,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent -(bool)shouldTryToHandleEvents { - for(NSWindow* uch in [self childWindows]) - { - auto ch = objc_cast(uch); - if(ch == nil) - continue; - - if(![ch isModal]) - continue; - - return FALSE; - } - return TRUE; -} - --(bool) isModal -{ - return _isModal; + return _isEnabled; } --(void) setModal: (bool) isModal +-(void) setEnabled:(bool)enable { - _isModal = isModal; + _isEnabled = enable; } -(void)makeKeyWindow diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index fa1e955153..324ff06452 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -62,30 +62,26 @@ namespace Avalonia.Animation } } - protected override void OnPropertyChanged( - AvaloniaProperty property, - Optional oldValue, - BindingValue newValue, - BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - if (_transitions is null || _previousTransitions is null || priority == BindingPriority.Animation) + if (_transitions is null || _previousTransitions is null || change.Priority == BindingPriority.Animation) return; // PERF-SENSITIVE: Called on every property change. Don't use LINQ here (too many allocations). foreach (var transition in _transitions) { - if (transition.Property == property) + if (transition.Property == change.Property) { - if (_previousTransitions.TryGetValue(property, out var dispose)) + if (_previousTransitions.TryGetValue(change.Property, out var dispose)) dispose.Dispose(); var instance = transition.Apply( this, Clock ?? Avalonia.Animation.Clock.GlobalClock, - oldValue.GetValueOrDefault(), - newValue.GetValueOrDefault()); + change.OldValue.GetValueOrDefault(), + change.NewValue.GetValueOrDefault()); - _previousTransitions[property] = instance; + _previousTransitions[change.Property] = instance; return; } } diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index ed36e6da43..f387d7e0b6 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -259,6 +259,21 @@ namespace Avalonia return registered.InvokeGetter(this); } + /// + public Optional GetBaseValue(StyledPropertyBase property, BindingPriority maxPriority) + { + property = property ?? throw new ArgumentNullException(nameof(property)); + VerifyAccess(); + + if (_values is object && + _values.TryGetValue(property, maxPriority, out var value)) + { + return value; + } + + return default; + } + /// /// Checks whether a is animating. /// @@ -458,29 +473,43 @@ namespace Avalonia return _propertyChanged?.GetInvocationList(); } - void IValueSink.ValueChanged( - StyledPropertyBase property, - BindingPriority priority, - Optional oldValue, - BindingValue newValue) + void IValueSink.ValueChanged(AvaloniaPropertyChangedEventArgs change) { - oldValue = oldValue.HasValue ? oldValue : GetInheritedOrDefault(property); - newValue = newValue.HasValue ? newValue : newValue.WithValue(GetInheritedOrDefault(property)); + var property = (StyledPropertyBase)change.Property; - LogIfError(property, newValue); + LogIfError(property, change.NewValue); - if (!EqualityComparer.Default.Equals(oldValue.Value, newValue.Value)) + // If the change is to the effective value of the property and no old/new value is set + // then fill in the old/new value from property inheritance/default value. We don't do + // this for non-effective value changes because these are only needed for property + // transitions, where knowing e.g. that an inherited value is active at an arbitrary + // priority isn't of any use and would introduce overhead. + if (change.IsEffectiveValueChange && !change.OldValue.HasValue) { - RaisePropertyChanged(property, oldValue, newValue, priority); + change.SetOldValue(GetInheritedOrDefault(property)); + } - Logger.TryGet(LogEventLevel.Verbose)?.Log( - LogArea.Property, - this, - "{Property} changed from {$Old} to {$Value} with priority {Priority}", - property, - oldValue, - newValue, - (BindingPriority)priority); + if (change.IsEffectiveValueChange && !change.NewValue.HasValue) + { + change.SetNewValue(GetInheritedOrDefault(property)); + } + + if (!change.IsEffectiveValueChange || + !EqualityComparer.Default.Equals(change.OldValue.Value, change.NewValue.Value)) + { + RaisePropertyChanged(change); + + if (change.IsEffectiveValueChange) + { + Logger.TryGet(LogEventLevel.Verbose)?.Log( + LogArea.Property, + this, + "{Property} changed from {$Old} to {$Value} with priority {Priority}", + property, + change.OldValue, + change.NewValue, + change.Priority); + } } } @@ -489,7 +518,13 @@ namespace Avalonia IPriorityValueEntry entry, Optional oldValue) { - ((IValueSink)this).ValueChanged(property, BindingPriority.Unset, oldValue, default); + var change = new AvaloniaPropertyChangedEventArgs( + this, + property, + oldValue, + default, + BindingPriority.Unset); + ((IValueSink)this).ValueChanged(change); } /// @@ -575,15 +610,20 @@ namespace Avalonia /// /// Called when a avalonia property changes on the object. /// - /// The property whose value has changed. - /// The old value of the property. - /// The new value of the property. - /// The priority of the new value. - protected virtual void OnPropertyChanged( - AvaloniaProperty property, - Optional oldValue, - BindingValue newValue, - BindingPriority priority) + /// The property change details. + protected virtual void OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change) + { + if (change.IsEffectiveValueChange) + { + OnPropertyChanged(change); + } + } + + /// + /// Called when a avalonia property changes on the object. + /// + /// The property change details. + protected virtual void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { } @@ -600,57 +640,12 @@ namespace Avalonia BindingValue newValue, BindingPriority priority = BindingPriority.LocalValue) { - property = property ?? throw new ArgumentNullException(nameof(property)); - - VerifyAccess(); - - property.Notifying?.Invoke(this, true); - - try - { - AvaloniaPropertyChangedEventArgs e = null; - var hasChanged = property.HasChangedSubscriptions; - - if (hasChanged || _propertyChanged != null) - { - e = new AvaloniaPropertyChangedEventArgs( - this, - property, - oldValue, - newValue, - priority); - } - - OnPropertyChanged(property, oldValue, newValue, priority); - - if (hasChanged) - { - property.NotifyChanged(e); - } - - _propertyChanged?.Invoke(this, e); - - if (_inpcChanged != null) - { - var inpce = new PropertyChangedEventArgs(property.Name); - _inpcChanged(this, inpce); - } - - if (property.Inherits && _inheritanceChildren != null) - { - foreach (var child in _inheritanceChildren) - { - child.InheritedPropertyChanged( - property, - oldValue, - newValue.ToOptional()); - } - } - } - finally - { - property.Notifying?.Invoke(this, false); - } + RaisePropertyChanged(new AvaloniaPropertyChangedEventArgs( + this, + property, + oldValue, + newValue, + priority)); } /// @@ -689,7 +684,9 @@ namespace Avalonia return property.GetDefaultValue(GetType()); } - private T GetValueOrInheritedOrDefault(StyledPropertyBase property) + private T GetValueOrInheritedOrDefault( + StyledPropertyBase property, + BindingPriority maxPriority = BindingPriority.Animation) { var o = this; var inherits = property.Inherits; @@ -699,7 +696,7 @@ namespace Avalonia { var values = o._values; - if (values?.TryGetValue(property, out value) == true) + if (values?.TryGetValue(property, maxPriority, out value) == true) { return value; } @@ -715,6 +712,51 @@ namespace Avalonia return property.GetDefaultValue(GetType()); } + protected internal void RaisePropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + VerifyAccess(); + + if (change.IsEffectiveValueChange) + { + change.Property.Notifying?.Invoke(this, true); + } + + try + { + OnPropertyChangedCore(change); + + if (change.IsEffectiveValueChange) + { + change.Property.NotifyChanged(change); + _propertyChanged?.Invoke(this, change); + + if (_inpcChanged != null) + { + var inpce = new PropertyChangedEventArgs(change.Property.Name); + _inpcChanged(this, inpce); + } + + if (change.Property.Inherits && _inheritanceChildren != null) + { + foreach (var child in _inheritanceChildren) + { + child.InheritedPropertyChanged( + change.Property, + change.OldValue, + change.NewValue.ToOptional()); + } + } + } + } + finally + { + if (change.IsEffectiveValueChange) + { + change.Property.Notifying?.Invoke(this, false); + } + } + } + /// /// Sets the value of a direct property. /// diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 4fc65a3ed4..173c5c1a94 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -448,6 +448,65 @@ namespace Avalonia }; } + /// + /// Gets an base value. + /// + /// The object. + /// The property. + /// The maximum priority for the value. + /// + /// For styled properties, gets the value of the property if set on the object with a + /// priority equal or lower to , otherwise + /// . Note that this method does not return + /// property values that come from inherited or default values. + /// + /// For direct properties returns . + /// + public static object GetBaseValue( + this IAvaloniaObject target, + AvaloniaProperty property, + BindingPriority maxPriority) + { + target = target ?? throw new ArgumentNullException(nameof(target)); + property = property ?? throw new ArgumentNullException(nameof(property)); + + return property.RouteGetBaseValue(target, maxPriority); + } + + /// + /// Gets an base value. + /// + /// The object. + /// The property. + /// The maximum priority for the value. + /// + /// For styled properties, gets the value of the property if set on the object with a + /// priority equal or lower to , otherwise + /// . Note that this method does not return property values + /// that come from inherited or default values. + /// + /// For direct properties returns + /// . + /// + public static Optional GetBaseValue( + this IAvaloniaObject target, + AvaloniaProperty property, + BindingPriority maxPriority) + { + target = target ?? throw new ArgumentNullException(nameof(target)); + property = property ?? throw new ArgumentNullException(nameof(property)); + + target = target ?? throw new ArgumentNullException(nameof(target)); + property = property ?? throw new ArgumentNullException(nameof(property)); + + return property switch + { + StyledPropertyBase styled => target.GetBaseValue(styled, maxPriority), + DirectPropertyBase direct => target.GetValue(direct), + _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") + }; + } + /// /// Sets a value. /// diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 74d7039751..daa7191cc5 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -496,6 +496,13 @@ namespace Avalonia /// The object instance. internal abstract object RouteGetValue(IAvaloniaObject o); + /// + /// Routes an untyped GetBaseValue call to a typed call. + /// + /// The object instance. + /// The maximum priority for the value. + internal abstract object RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority); + /// /// Routes an untyped SetValue call to a typed call. /// diff --git a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs index 0f09747865..c1a2832fde 100644 --- a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs +++ b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs @@ -16,6 +16,7 @@ namespace Avalonia { Sender = sender; Priority = priority; + IsEffectiveValueChange = true; } /// @@ -35,19 +36,11 @@ namespace Avalonia /// /// Gets the old value of the property. /// - /// - /// The old value of the property or if the - /// property previously had no value. - /// public object? OldValue => GetOldValue(); /// /// Gets the new value of the property. /// - /// - /// The new value of the property or if the - /// property previously had no value. - /// public object? NewValue => GetNewValue(); /// @@ -58,6 +51,20 @@ namespace Avalonia /// public BindingPriority Priority { get; private set; } + /// + /// Gets a value indicating whether the change represents a change to the effective value of + /// the property. + /// + /// + /// This will usually be true, except in + /// + /// which recieves notifications for all changes to property values, whether a value with a higher + /// priority is present or not. When this property is false, the change that is being signalled + /// has not resulted in a change to the property value on the object. + /// + public bool IsEffectiveValueChange { get; private set; } + + internal void MarkNonEffectiveValue() => IsEffectiveValueChange = false; protected abstract AvaloniaProperty GetProperty(); protected abstract object? GetOldValue(); protected abstract object? GetNewValue(); diff --git a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs index fca32b4ffc..054bf93b3a 100644 --- a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs +++ b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs @@ -1,4 +1,3 @@ -using System; using Avalonia.Data; #nullable enable @@ -6,7 +5,7 @@ using Avalonia.Data; namespace Avalonia { /// - /// Provides information for a avalonia property change. + /// Provides information for an Avalonia property change. /// public class AvaloniaPropertyChangedEventArgs : AvaloniaPropertyChangedEventArgs { @@ -42,19 +41,28 @@ namespace Avalonia /// /// Gets the old value of the property. /// - /// - /// The old value of the property. - /// + /// + /// When is true, returns the + /// old value of the property on the object. + /// When is false, returns + /// . + /// public new Optional OldValue { get; private set; } /// /// Gets the new value of the property. /// - /// - /// The new value of the property. - /// + /// + /// When is true, returns the + /// value of the property on the object. + /// When is false returns the + /// changed value, or if the value was removed. + /// public new BindingValue NewValue { get; private set; } + internal void SetOldValue(Optional value) => OldValue = value; + internal void SetNewValue(BindingValue value) => NewValue = value; + protected override AvaloniaProperty GetProperty() => Property; protected override object? GetOldValue() => OldValue.GetValueOrDefault(AvaloniaProperty.UnsetValue); diff --git a/src/Avalonia.Base/Data/BindingValue.cs b/src/Avalonia.Base/Data/BindingValue.cs index cecdd33e7b..9aac1bacba 100644 --- a/src/Avalonia.Base/Data/BindingValue.cs +++ b/src/Avalonia.Base/Data/BindingValue.cs @@ -348,8 +348,8 @@ namespace Avalonia.Data return new BindingValue( fallbackValue.HasValue ? - BindingValueType.DataValidationError : - BindingValueType.DataValidationErrorWithFallback, + BindingValueType.DataValidationErrorWithFallback : + BindingValueType.DataValidationError, fallbackValue.HasValue ? fallbackValue.Value : default, e); } diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index d3b5277c53..0e65379abd 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -120,6 +120,11 @@ namespace Avalonia return o.GetValue(this); } + internal override object RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority) + { + return o.GetValue(this); + } + /// internal override IDisposable? RouteSetValue( IAvaloniaObject o, diff --git a/src/Avalonia.Base/IAvaloniaObject.cs b/src/Avalonia.Base/IAvaloniaObject.cs index 867249bf0e..0452f77d4c 100644 --- a/src/Avalonia.Base/IAvaloniaObject.cs +++ b/src/Avalonia.Base/IAvaloniaObject.cs @@ -41,6 +41,19 @@ namespace Avalonia /// The value. T GetValue(DirectPropertyBase property); + /// + /// Gets an base value. + /// + /// The type of the property. + /// The property. + /// The maximum priority for the value. + /// + /// Gets the value of the property, if set on this object with a priority equal or lower to + /// , otherwise . Note that + /// this method does not return property values that come from inherited or default values. + /// + Optional GetBaseValue(StyledPropertyBase property, BindingPriority maxPriority); + /// /// Checks whether a is animating. /// diff --git a/src/Avalonia.Base/PropertyStore/BindingEntry.cs b/src/Avalonia.Base/PropertyStore/BindingEntry.cs index 3249b31d66..0d563947e7 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntry.cs @@ -22,6 +22,7 @@ namespace Avalonia.PropertyStore private readonly IAvaloniaObject _owner; private IValueSink _sink; private IDisposable? _subscription; + private Optional _value; public BindingEntry( IAvaloniaObject owner, @@ -40,18 +41,21 @@ namespace Avalonia.PropertyStore public StyledPropertyBase Property { get; } public BindingPriority Priority { get; } public IObservable> Source { get; } - public Optional Value { get; private set; } - Optional IValue.Value => Value.ToObject(); - BindingPriority IValue.ValuePriority => Priority; + Optional IValue.GetValue() => _value.ToObject(); + + public Optional GetValue(BindingPriority maxPriority) + { + return Priority >= maxPriority ? _value : Optional.Empty; + } public void Dispose() { _subscription?.Dispose(); _subscription = null; - _sink.Completed(Property, this, Value); + _sink.Completed(Property, this, _value); } - public void OnCompleted() => _sink.Completed(Property, this, Value); + public void OnCompleted() => _sink.Completed(Property, this, _value); public void OnError(Exception error) { @@ -94,14 +98,14 @@ namespace Avalonia.PropertyStore return; } - var old = Value; + var old = _value; if (value.Type != BindingValueType.DataValidationError) { - Value = value.ToOptional(); + _value = value.ToOptional(); } - _sink.ValueChanged(Property, Priority, old, value); + _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs(_owner, Property, old, value, Priority)); } } } diff --git a/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs b/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs index aa054c46ff..46f6f9a137 100644 --- a/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs @@ -13,6 +13,7 @@ namespace Avalonia.PropertyStore internal class ConstantValueEntry : IPriorityValueEntry, IDisposable { private IValueSink _sink; + private Optional _value; public ConstantValueEntry( StyledPropertyBase property, @@ -21,18 +22,21 @@ namespace Avalonia.PropertyStore IValueSink sink) { Property = property; - Value = value; + _value = value; Priority = priority; _sink = sink; } public StyledPropertyBase Property { get; } public BindingPriority Priority { get; } - public Optional Value { get; } - Optional IValue.Value => Value.ToObject(); - BindingPriority IValue.ValuePriority => Priority; + Optional IValue.GetValue() => _value.ToObject(); - public void Dispose() => _sink.Completed(Property, this, Value); + public Optional GetValue(BindingPriority maxPriority = BindingPriority.Animation) + { + return Priority >= maxPriority ? _value : Optional.Empty; + } + + public void Dispose() => _sink.Completed(Property, this, _value); public void Reparent(IValueSink sink) => _sink = sink; } } diff --git a/src/Avalonia.Base/PropertyStore/IValue.cs b/src/Avalonia.Base/PropertyStore/IValue.cs index 0ce7fb8308..249cfc360c 100644 --- a/src/Avalonia.Base/PropertyStore/IValue.cs +++ b/src/Avalonia.Base/PropertyStore/IValue.cs @@ -9,8 +9,8 @@ namespace Avalonia.PropertyStore /// internal interface IValue { - Optional Value { get; } - BindingPriority ValuePriority { get; } + Optional GetValue(); + BindingPriority Priority { get; } } /// @@ -19,6 +19,6 @@ namespace Avalonia.PropertyStore /// The property type. internal interface IValue : IValue { - new Optional Value { get; } + Optional GetValue(BindingPriority maxPriority = BindingPriority.Animation); } } diff --git a/src/Avalonia.Base/PropertyStore/IValueSink.cs b/src/Avalonia.Base/PropertyStore/IValueSink.cs index 9012a985ac..3a1e9731d8 100644 --- a/src/Avalonia.Base/PropertyStore/IValueSink.cs +++ b/src/Avalonia.Base/PropertyStore/IValueSink.cs @@ -9,11 +9,7 @@ namespace Avalonia.PropertyStore /// internal interface IValueSink { - void ValueChanged( - StyledPropertyBase property, - BindingPriority priority, - Optional oldValue, - BindingValue newValue); + void ValueChanged(AvaloniaPropertyChangedEventArgs change); void Completed( StyledPropertyBase property, diff --git a/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs b/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs index 22258390da..59c017bc09 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs @@ -14,9 +14,14 @@ namespace Avalonia.PropertyStore private T _value; public LocalValueEntry(T value) => _value = value; - public Optional Value => _value; - public BindingPriority ValuePriority => BindingPriority.LocalValue; - Optional IValue.Value => Value.ToObject(); + public BindingPriority Priority => BindingPriority.LocalValue; + Optional IValue.GetValue() => new Optional(_value); + + public Optional GetValue(BindingPriority maxPriority) + { + return BindingPriority.LocalValue >= maxPriority ? _value : Optional.Empty; + } + public void SetValue(T value) => _value = value; } } diff --git a/src/Avalonia.Base/PropertyStore/PriorityValue.cs b/src/Avalonia.Base/PropertyStore/PriorityValue.cs index affb20f334..5e223cad60 100644 --- a/src/Avalonia.Base/PropertyStore/PriorityValue.cs +++ b/src/Avalonia.Base/PropertyStore/PriorityValue.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using Avalonia.Data; #nullable enable @@ -24,6 +25,7 @@ namespace Avalonia.PropertyStore private readonly List> _entries = new List>(); private readonly Func? _coerceValue; private Optional _localValue; + private Optional _value; public PriorityValue( IAvaloniaObject owner, @@ -50,11 +52,13 @@ namespace Avalonia.PropertyStore { existing.Reparent(this); _entries.Add(existing); + + var v = existing.GetValue(); - if (existing.Value.HasValue) + if (v.HasValue) { - Value = existing.Value; - ValuePriority = existing.Priority; + _value = v; + Priority = existing.Priority; } } @@ -65,18 +69,39 @@ namespace Avalonia.PropertyStore LocalValueEntry existing) : this(owner, property, sink) { - _localValue = existing.Value; - Value = _localValue; - ValuePriority = BindingPriority.LocalValue; + _value = _localValue = existing.GetValue(BindingPriority.LocalValue); + Priority = BindingPriority.LocalValue; } public StyledPropertyBase Property { get; } - public Optional Value { get; private set; } - public BindingPriority ValuePriority { get; private set; } + public BindingPriority Priority { get; private set; } = BindingPriority.Unset; public IReadOnlyList> Entries => _entries; - Optional IValue.Value => Value.ToObject(); + Optional IValue.GetValue() => _value.ToObject(); + + public void ClearLocalValue() + { + UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs( + _owner, + Property, + default, + default, + BindingPriority.LocalValue)); + } + + public Optional GetValue(BindingPriority maxPriority = BindingPriority.Animation) + { + if (Priority == BindingPriority.Unset) + { + return default; + } - public void ClearLocalValue() => UpdateEffectiveValue(); + if (Priority >= maxPriority) + { + return _value; + } + + return CalculateValue(maxPriority).Item1; + } public IDisposable? SetValue(T value, BindingPriority priority) { @@ -94,7 +119,13 @@ namespace Avalonia.PropertyStore result = entry; } - UpdateEffectiveValue(); + UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs( + _owner, + Property, + default, + value, + priority)); + return result; } @@ -106,20 +137,19 @@ namespace Avalonia.PropertyStore return binding; } - public void CoerceValue() => UpdateEffectiveValue(); + public void CoerceValue() => UpdateEffectiveValue(null); - void IValueSink.ValueChanged( - StyledPropertyBase property, - BindingPriority priority, - Optional oldValue, - BindingValue newValue) + void IValueSink.ValueChanged(AvaloniaPropertyChangedEventArgs change) { - if (priority == BindingPriority.LocalValue) + if (change.Priority == BindingPriority.LocalValue) { _localValue = default; } - UpdateEffectiveValue(); + if (change is AvaloniaPropertyChangedEventArgs c) + { + UpdateEffectiveValue(c); + } } void IValueSink.Completed( @@ -128,7 +158,16 @@ namespace Avalonia.PropertyStore Optional oldValue) { _entries.Remove((IPriorityValueEntry)entry); - UpdateEffectiveValue(); + + if (oldValue is Optional o) + { + UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs( + _owner, + Property, + o, + default, + entry.Priority)); + } } private int FindInsertPoint(BindingPriority priority) @@ -147,53 +186,73 @@ namespace Avalonia.PropertyStore return result; } - private void UpdateEffectiveValue() + public (Optional, BindingPriority) CalculateValue(BindingPriority maxPriority) { var reachedLocalValues = false; - var value = default(Optional); - if (_entries.Count > 0) + for (var i = _entries.Count - 1; i >= 0; --i) { - for (var i = _entries.Count - 1; i >= 0; --i) + var entry = _entries[i]; + + if (entry.Priority < maxPriority) + { + continue; + } + + if (!reachedLocalValues && + entry.Priority >= BindingPriority.LocalValue && + maxPriority <= BindingPriority.LocalValue && + _localValue.HasValue) + { + return (_localValue, BindingPriority.LocalValue); + } + + var entryValue = entry.GetValue(); + + if (entryValue.HasValue) { - var entry = _entries[i]; - - if (!reachedLocalValues && entry.Priority >= BindingPriority.LocalValue) - { - reachedLocalValues = true; - - if (_localValue.HasValue) - { - value = _localValue; - ValuePriority = BindingPriority.LocalValue; - break; - } - } - - if (entry.Value.HasValue) - { - value = entry.Value; - ValuePriority = entry.Priority; - break; - } + return (entryValue, entry.Priority); } } - else if (_localValue.HasValue) + + if (!reachedLocalValues && + maxPriority <= BindingPriority.LocalValue && + _localValue.HasValue) { - value = _localValue; - ValuePriority = BindingPriority.LocalValue; + return (_localValue, BindingPriority.LocalValue); } + return (default, BindingPriority.Unset); + } + + private void UpdateEffectiveValue(AvaloniaPropertyChangedEventArgs? change) + { + var (value, priority) = CalculateValue(BindingPriority.Animation); + if (value.HasValue && _coerceValue != null) { value = _coerceValue(_owner, value.Value); } - if (value != Value) + Priority = priority; + + if (value != _value) + { + var old = _value; + _value = value; + + _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs( + _owner, + Property, + old, + value, + Priority)); + } + else if (change is object) { - var old = Value; - Value = value; - _sink.ValueChanged(Property, ValuePriority, old, value); + change.MarkNonEffectiveValue(); + change.SetOldValue(default); + _sink.ValueChanged(change); } } } diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs index 1f88bfb2aa..3e92c3bdf7 100644 --- a/src/Avalonia.Base/StyledPropertyBase.cs +++ b/src/Avalonia.Base/StyledPropertyBase.cs @@ -197,6 +197,13 @@ namespace Avalonia return o.GetValue(this); } + /// + internal override object RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority) + { + var value = o.GetBaseValue(this, maxPriority); + return value.HasValue ? value.Value : AvaloniaProperty.UnsetValue; + } + /// internal override IDisposable RouteSetValue( IAvaloniaObject o, diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs index 104c06de0f..05e66f2e0a 100644 --- a/src/Avalonia.Base/ValueStore.cs +++ b/src/Avalonia.Base/ValueStore.cs @@ -37,7 +37,7 @@ namespace Avalonia { if (_values.TryGetValue(property, out var slot)) { - return slot.ValuePriority < BindingPriority.LocalValue; + return slot.Priority < BindingPriority.LocalValue; } return false; @@ -47,21 +47,24 @@ namespace Avalonia { if (_values.TryGetValue(property, out var slot)) { - return slot.Value.HasValue; + return slot.GetValue().HasValue; } return false; } - public bool TryGetValue(StyledPropertyBase property, out T value) + public bool TryGetValue( + StyledPropertyBase property, + BindingPriority maxPriority, + out T value) { if (_values.TryGetValue(property, out var slot)) { - var v = (IValue)slot; + var v = ((IValue)slot).GetValue(maxPriority); - if (v.Value.HasValue) + if (v.HasValue) { - value = v.Value.Value; + value = v.Value; return true; } } @@ -90,17 +93,22 @@ namespace Avalonia _values.AddValue(property, entry); result = entry.SetValue(value, priority); } - else if (priority == BindingPriority.LocalValue) - { - _values.AddValue(property, new LocalValueEntry(value)); - _sink.ValueChanged(property, priority, default, value); - } else { - var entry = new ConstantValueEntry(property, value, priority, this); - _values.AddValue(property, entry); - _sink.ValueChanged(property, priority, default, value); - result = entry; + var change = new AvaloniaPropertyChangedEventArgs(_owner, property, default, value, priority); + + if (priority == BindingPriority.LocalValue) + { + _values.AddValue(property, new LocalValueEntry(value)); + _sink.ValueChanged(change); + } + else + { + var entry = new ConstantValueEntry(property, value, priority, this); + _values.AddValue(property, entry); + _sink.ValueChanged(change); + result = entry; + } } return result; @@ -149,13 +157,14 @@ namespace Avalonia if (remove) { - var old = TryGetValue(property, out var value) ? value : default; + var old = TryGetValue(property, BindingPriority.LocalValue, out var value) ? value : default; _values.Remove(property); - _sink.ValueChanged( + _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs( + _owner, property, - BindingPriority.Unset, old, - BindingValue.Unset); + default, + BindingPriority.Unset)); } } } @@ -176,23 +185,20 @@ namespace Avalonia { if (_values.TryGetValue(property, out var slot)) { + var slotValue = slot.GetValue(); return new Diagnostics.AvaloniaPropertyValue( property, - slot.Value.HasValue ? slot.Value.Value : AvaloniaProperty.UnsetValue, - slot.ValuePriority, + slotValue.HasValue ? slotValue.Value : AvaloniaProperty.UnsetValue, + slot.Priority, null); } return null; } - void IValueSink.ValueChanged( - StyledPropertyBase property, - BindingPriority priority, - Optional oldValue, - BindingValue newValue) + void IValueSink.ValueChanged(AvaloniaPropertyChangedEventArgs change) { - _sink.ValueChanged(property, priority, oldValue, newValue); + _sink.ValueChanged(change); } void IValueSink.Completed( @@ -232,9 +238,14 @@ namespace Avalonia { if (priority == BindingPriority.LocalValue) { - var old = l.Value; + var old = l.GetValue(BindingPriority.LocalValue); l.SetValue(value); - _sink.ValueChanged(property, priority, old, value); + _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs( + _owner, + property, + old, + value, + priority)); } else { diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 3a1e612a05..cfe47a09d5 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -1,5 +1,4 @@ -// (c) Copyright Microsoft Corporation. -// This source is subject to the Microsoft Public License (Ms-PL). +// This source is subject to the Microsoft Public License (Ms-PL). // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. @@ -767,7 +766,7 @@ namespace Avalonia.Controls /// /// ItemsProperty property changed handler. /// - /// AvaloniaPropertyChangedEventArgs. + /// The event arguments. private void OnItemsPropertyChanged(AvaloniaPropertyChangedEventArgs e) { if (!_areHandlersSuspended) diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 136e8ed851..b54eb2ac57 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -313,17 +313,13 @@ namespace Avalonia.Controls IsPressed = false; } - protected override void OnPropertyChanged( - AvaloniaProperty property, - Optional oldValue, - BindingValue newValue, - BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - base.OnPropertyChanged(property, oldValue, newValue, priority); + base.OnPropertyChanged(change); - if (property == IsPressedProperty) + if (change.Property == IsPressedProperty) { - UpdatePseudoClasses(newValue.GetValueOrDefault()); + UpdatePseudoClasses(change.NewValue.GetValueOrDefault()); } } diff --git a/src/Avalonia.Controls/ButtonSpinner.cs b/src/Avalonia.Controls/ButtonSpinner.cs index 7945d63b06..44f66d397a 100644 --- a/src/Avalonia.Controls/ButtonSpinner.cs +++ b/src/Avalonia.Controls/ButtonSpinner.cs @@ -205,17 +205,13 @@ namespace Avalonia.Controls } } - protected override void OnPropertyChanged( - AvaloniaProperty property, - Optional oldValue, - BindingValue newValue, - BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - base.OnPropertyChanged(property, oldValue, newValue, priority); + base.OnPropertyChanged(change); - if (property == ButtonSpinnerLocationProperty) + if (change.Property == ButtonSpinnerLocationProperty) { - UpdatePseudoClasses(newValue.GetValueOrDefault()); + UpdatePseudoClasses(change.NewValue.GetValueOrDefault()); } } diff --git a/src/Avalonia.Controls/Calendar/DatePicker.cs b/src/Avalonia.Controls/Calendar/DatePicker.cs index 7cd0230b36..0f53dc1364 100644 --- a/src/Avalonia.Controls/Calendar/DatePicker.cs +++ b/src/Avalonia.Controls/Calendar/DatePicker.cs @@ -510,17 +510,13 @@ namespace Avalonia.Controls } } - protected override void OnPropertyChanged( - AvaloniaProperty property, - Optional oldValue, - BindingValue newValue, - BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - base.OnPropertyChanged(property, oldValue, newValue, priority); + base.OnPropertyChanged(change); - if (property == SelectedDateProperty) + if (change.Property == SelectedDateProperty) { - DataValidationErrors.SetError(this, newValue.Error); + DataValidationErrors.SetError(this, change.NewValue.Error); } } diff --git a/src/Avalonia.Controls/Expander.cs b/src/Avalonia.Controls/Expander.cs index e42d3ec1e5..43882b70c8 100644 --- a/src/Avalonia.Controls/Expander.cs +++ b/src/Avalonia.Controls/Expander.cs @@ -78,17 +78,13 @@ namespace Avalonia.Controls } } - protected override void OnPropertyChanged( - AvaloniaProperty property, - Optional oldValue, - BindingValue newValue, - BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - base.OnPropertyChanged(property, oldValue, newValue, priority); + base.OnPropertyChanged(change); - if (property == ExpandDirectionProperty) + if (change.Property == ExpandDirectionProperty) { - UpdatePseudoClasses(newValue.GetValueOrDefault()); + UpdatePseudoClasses(change.NewValue.GetValueOrDefault()); } } diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs index 0cfea2c68b..6d9f6b8b77 100644 --- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs +++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs @@ -136,17 +136,13 @@ namespace Avalonia.Controls.Notifications notificationControl.Close(); } - protected override void OnPropertyChanged( - AvaloniaProperty property, - Optional oldValue, - BindingValue newValue, - BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - base.OnPropertyChanged(property, oldValue, newValue, priority); + base.OnPropertyChanged(change); - if (property == PositionProperty) + if (change.Property == PositionProperty) { - UpdatePseudoClasses(newValue.GetValueOrDefault()); + UpdatePseudoClasses(change.NewValue.GetValueOrDefault()); } } diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 5fa0ec57b5..cf31d30332 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -26,9 +26,21 @@ namespace Avalonia.Platform void SetTitle(string title); /// - /// Shows the window as a dialog. + /// Sets the parent of the window. /// - void ShowDialog(IWindowImpl parent); + /// The parent . + void SetParent(IWindowImpl parent); + + /// + /// Disables the window for example when a modal dialog is open. + /// + /// true if the window is enabled, or false if it is disabled. + void SetEnabled(bool enable); + + /// + /// Called when a disabled window received input. Can be used to activate child windows. + /// + Action GotInputWhenDisabled { get; set; } /// /// Enables or disables system window decorations (title bar, buttons, etc) diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs index 2e6292b4b4..f6186a29a9 100644 --- a/src/Avalonia.Controls/Primitives/ScrollBar.cs +++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs @@ -123,24 +123,20 @@ namespace Avalonia.Controls.Primitives } } - protected override void OnPropertyChanged( - AvaloniaProperty property, - Optional oldValue, - BindingValue newValue, - BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - base.OnPropertyChanged(property, oldValue, newValue, priority); + base.OnPropertyChanged(change); - if (property == OrientationProperty) + if (change.Property == OrientationProperty) { - UpdatePseudoClasses(newValue.GetValueOrDefault()); + UpdatePseudoClasses(change.NewValue.GetValueOrDefault()); } else { - if (property == MinimumProperty || - property == MaximumProperty || - property == ViewportSizeProperty || - property == VisibilityProperty) + if (change.Property == MinimumProperty || + change.Property == MaximumProperty || + change.Property == ViewportSizeProperty || + change.Property == VisibilityProperty) { UpdateIsVisible(); } diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 8fc1a55e68..c915dc70b6 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -455,13 +455,13 @@ namespace Avalonia.Controls.Primitives InternalEndInit(); } - protected override void OnPropertyChanged(AvaloniaProperty property, Optional oldValue, BindingValue newValue, BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - base.OnPropertyChanged(property, oldValue, newValue, priority); + base.OnPropertyChanged(change); - if (property == SelectionModeProperty) + if (change.Property == SelectionModeProperty) { - var mode = newValue.GetValueOrDefault(); + var mode = change.NewValue.GetValueOrDefault(); Selection.SingleSelect = !mode.HasFlagCustom(SelectionMode.Multiple); Selection.AutoSelect = mode.HasFlagCustom(SelectionMode.AlwaysSelected); } diff --git a/src/Avalonia.Controls/Primitives/Track.cs b/src/Avalonia.Controls/Primitives/Track.cs index 1e02d70fff..e104a8a664 100644 --- a/src/Avalonia.Controls/Primitives/Track.cs +++ b/src/Avalonia.Controls/Primitives/Track.cs @@ -280,17 +280,13 @@ namespace Avalonia.Controls.Primitives return arrangeSize; } - protected override void OnPropertyChanged( - AvaloniaProperty property, - Optional oldValue, - BindingValue newValue, - BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - base.OnPropertyChanged(property, oldValue, newValue, priority); + base.OnPropertyChanged(change); - if (property == OrientationProperty) + if (change.Property == OrientationProperty) { - UpdatePseudoClasses(newValue.GetValueOrDefault()); + UpdatePseudoClasses(change.NewValue.GetValueOrDefault()); } } diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 9225625bf5..c7356f2b4d 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -83,21 +83,17 @@ namespace Avalonia.Controls return base.ArrangeOverride(finalSize); } - protected override void OnPropertyChanged( - AvaloniaProperty property, - Optional oldValue, - BindingValue newValue, - BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - base.OnPropertyChanged(property, oldValue, newValue, priority); + base.OnPropertyChanged(change); - if (property == IsIndeterminateProperty) + if (change.Property == IsIndeterminateProperty) { - UpdatePseudoClasses(newValue.GetValueOrDefault(), null); + UpdatePseudoClasses(change.NewValue.GetValueOrDefault(), null); } - else if (property == OrientationProperty) + else if (change.Property == OrientationProperty) { - UpdatePseudoClasses(null, newValue.GetValueOrDefault()); + UpdatePseudoClasses(null, change.NewValue.GetValueOrDefault()); } } diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index 12fba12c8c..069da6e9ac 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -376,12 +376,12 @@ namespace Avalonia.Controls _viewportManager.ResetScrollers(); } - protected override void OnPropertyChanged(AvaloniaProperty property, Optional oldValue, BindingValue newValue, BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - if (property == ItemsProperty) + if (change.Property == ItemsProperty) { - var oldEnumerable = oldValue.GetValueOrDefault(); - var newEnumerable = newValue.GetValueOrDefault(); + var oldEnumerable = change.OldValue.GetValueOrDefault(); + var newEnumerable = change.NewValue.GetValueOrDefault(); if (oldEnumerable != newEnumerable) { @@ -394,24 +394,28 @@ namespace Avalonia.Controls OnDataSourcePropertyChanged(ItemsSourceView, newDataSource); } } - else if (property == ItemTemplateProperty) + else if (change.Property == ItemTemplateProperty) { - OnItemTemplateChanged(oldValue.GetValueOrDefault(), newValue.GetValueOrDefault()); + OnItemTemplateChanged( + change.OldValue.GetValueOrDefault(), + change.NewValue.GetValueOrDefault()); } - else if (property == LayoutProperty) + else if (change.Property == LayoutProperty) { - OnLayoutChanged(oldValue.GetValueOrDefault(), newValue.GetValueOrDefault()); + OnLayoutChanged( + change.OldValue.GetValueOrDefault(), + change.NewValue.GetValueOrDefault()); } - else if (property == HorizontalCacheLengthProperty) + else if (change.Property == HorizontalCacheLengthProperty) { - _viewportManager.HorizontalCacheLength = newValue.GetValueOrDefault(); + _viewportManager.HorizontalCacheLength = change.NewValue.GetValueOrDefault(); } - else if (property == VerticalCacheLengthProperty) + else if (change.Property == VerticalCacheLengthProperty) { - _viewportManager.VerticalCacheLength = newValue.GetValueOrDefault(); + _viewportManager.VerticalCacheLength = change.NewValue.GetValueOrDefault(); } - base.OnPropertyChanged(property, oldValue, newValue, priority); + base.OnPropertyChanged(change); } internal IControl GetElementImpl(int index, bool forceCreate, bool supressAutoRecycle) diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index f243372926..e92c8faf20 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -134,17 +134,13 @@ namespace Avalonia.Controls } } - protected override void OnPropertyChanged( - AvaloniaProperty property, - Optional oldValue, - BindingValue newValue, - BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - base.OnPropertyChanged(property, oldValue, newValue, priority); + base.OnPropertyChanged(change); - if (property == OrientationProperty) + if (change.Property == OrientationProperty) { - UpdatePseudoClasses(newValue.GetValueOrDefault()); + UpdatePseudoClasses(change.NewValue.GetValueOrDefault()); } } diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index a55274afb3..95e7437838 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -486,13 +486,13 @@ namespace Avalonia.Controls } } - protected override void OnPropertyChanged(AvaloniaProperty property, Optional oldValue, BindingValue newValue, BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - base.OnPropertyChanged(property, oldValue, newValue, priority); + base.OnPropertyChanged(change); - if (property == SelectionModeProperty) + if (change.Property == SelectionModeProperty) { - var mode = newValue.GetValueOrDefault(); + var mode = change.NewValue.GetValueOrDefault(); Selection.SingleSelect = !mode.HasFlagCustom(SelectionMode.Multiple); Selection.AutoSelect = mode.HasFlagCustom(SelectionMode.AlwaysSelected); } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 7dacf4b2af..474d845905 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reactive.Linq; @@ -68,6 +69,8 @@ namespace Avalonia.Controls /// public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot { + private List _children = new List(); + /// /// Defines the property. /// @@ -131,7 +134,7 @@ namespace Avalonia.Controls /// public static readonly RoutedEvent WindowClosedEvent = RoutedEvent.Register("WindowClosed", RoutingStrategies.Direct); - + /// /// Routed event that can be used for global tracking of opening windows /// @@ -183,6 +186,7 @@ namespace Avalonia.Controls : base(impl) { impl.Closing = HandleClosing; + impl.GotInputWhenDisabled = OnGotInputWhenDisabled; impl.WindowStateChanged = HandleWindowStateChanged; _maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size); this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x)); @@ -302,7 +306,7 @@ namespace Avalonia.Controls PlatformImpl?.Move(value); } } - + /// /// Starts moving a window with left button being held. Should be called from left mouse button press event handler /// @@ -323,7 +327,7 @@ namespace Avalonia.Controls /// /// Fired before a window is closed. /// - public event EventHandler Closing; + public event EventHandler Closing; /// /// Closes the window. @@ -365,19 +369,59 @@ namespace Avalonia.Controls { if (close) { - PlatformImpl?.Dispose(); + CloseInternal(); } } } + private void CloseInternal() + { + foreach (var child in _children.ToList()) + { + // if we HandleClosing() before then there will be no children. + child.CloseInternal(); + } + + if (Owner is Window owner) + { + owner.RemoveChild(this); + } + + Owner = null; + + PlatformImpl?.Dispose(); + } + /// /// Handles a closing notification from . /// protected virtual bool HandleClosing() { - var args = new CancelEventArgs(); - OnClosing(args); - return args.Cancel; + bool canClose = true; + + foreach (var child in _children.ToList()) + { + if (!child.HandleClosing()) + { + child.CloseInternal(); + } + else + { + canClose = false; + } + } + + if (canClose) + { + var args = new CancelEventArgs(); + OnClosing(args); + + return args.Cancel; + } + else + { + return !canClose; + } } protected virtual void HandleWindowStateChanged(WindowState state) @@ -407,6 +451,14 @@ namespace Avalonia.Controls using (BeginAutoSizing()) { Renderer?.Stop(); + + if (Owner is Window owner) + { + owner.RemoveChild(this); + } + + Owner = null; + PlatformImpl?.Hide(); } @@ -519,7 +571,10 @@ namespace Avalonia.Controls using (BeginAutoSizing()) { - PlatformImpl?.ShowDialog(owner.PlatformImpl); + PlatformImpl.SetParent(owner.PlatformImpl); + Owner = owner; + owner.AddChild(this); + PlatformImpl?.Show(); Renderer?.Start(); @@ -541,6 +596,37 @@ namespace Avalonia.Controls return result.Task; } + private void UpdateEnabled() + { + PlatformImpl.SetEnabled(_children.Count == 0); + } + + private void AddChild(Window window) + { + _children.Add(window); + UpdateEnabled(); + } + + private void RemoveChild(Window window) + { + _children.Remove(window); + UpdateEnabled(); + } + + private void OnGotInputWhenDisabled() + { + var firstChild = _children.FirstOrDefault(); + + if (firstChild != null) + { + firstChild.OnGotInputWhenDisabled(); + } + else + { + Activate(); + } + } + private void SetWindowStartupLocation(IWindowBaseImpl owner = null) { var scaling = owner?.Scaling ?? PlatformImpl?.Scaling ?? 1; @@ -631,6 +717,13 @@ namespace Avalonia.Controls RaiseEvent(new RoutedEventArgs(WindowClosedEvent)); base.HandleClosed(); + + if (Owner is Window owner) + { + owner.RemoveChild(this); + } + + Owner = null; } /// @@ -658,19 +751,15 @@ namespace Avalonia.Controls /// protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e); - protected override void OnPropertyChanged( - AvaloniaProperty property, - Optional oldValue, - BindingValue newValue, - BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - if (property == SystemDecorationsProperty) + if (change.Property == SystemDecorationsProperty) { - var typedNewValue = newValue.GetValueOrDefault(); + var typedNewValue = change.NewValue.GetValueOrDefault(); PlatformImpl?.SetSystemDecorations(typedNewValue); - var o = oldValue.GetValueOrDefault() == SystemDecorations.Full; + var o = change.OldValue.GetValueOrDefault() == SystemDecorations.Full; var n = typedNewValue == SystemDecorations.Full; if (o != n) diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index bb63d1b353..afc01db506 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -109,7 +109,7 @@ namespace Avalonia.Controls public WindowBase Owner { get { return _owner; } - set { SetAndRaise(OwnerProperty, ref _owner, value); } + protected set { SetAndRaise(OwnerProperty, ref _owner, value); } } /// diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 7480b3519c..844489ef97 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -83,6 +83,7 @@ namespace Avalonia.DesignerSupport.Remote } public IScreenImpl Screen { get; } = new ScreenStub(); + public Action GotInputWhenDisabled { get; set; } public void Activate() { @@ -115,5 +116,13 @@ namespace Avalonia.DesignerSupport.Remote public void SetTopmost(bool value) { } + + public void SetParent(IWindowImpl parent) + { + } + + public void SetEnabled(bool enable) + { + } } } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 82950ce53b..484cf3bc97 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -130,7 +130,17 @@ namespace Avalonia.DesignerSupport.Remote { } + public void SetParent(IWindowImpl parent) + { + } + + public void SetEnabled(bool enable) + { + } + public IPopupPositioner PopupPositioner { get; } + + public Action GotInputWhenDisabled { get; set; } } class ClipboardStub : IClipboard diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 20c775f965..407b28b665 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -526,17 +526,17 @@ namespace Avalonia.Input { } - protected override void OnPropertyChanged(AvaloniaProperty property, Optional oldValue, BindingValue newValue, BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - base.OnPropertyChanged(property, oldValue, newValue, priority); + base.OnPropertyChanged(change); - if (property == IsFocusedProperty) + if (change.Property == IsFocusedProperty) { - UpdatePseudoClasses(newValue.GetValueOrDefault(), null); + UpdatePseudoClasses(change.NewValue.GetValueOrDefault(), null); } - else if (property == IsPointerOverProperty) + else if (change.Property == IsPointerOverProperty) { - UpdatePseudoClasses(null, newValue.GetValueOrDefault()); + UpdatePseudoClasses(null, change.NewValue.GetValueOrDefault()); } } diff --git a/src/Avalonia.Layout/StackLayout.cs b/src/Avalonia.Layout/StackLayout.cs index d11875b2c1..1a90d2a2e0 100644 --- a/src/Avalonia.Layout/StackLayout.cs +++ b/src/Avalonia.Layout/StackLayout.cs @@ -312,11 +312,11 @@ namespace Avalonia.Layout InvalidateLayout(); } - protected override void OnPropertyChanged(AvaloniaProperty property, Optional oldValue, BindingValue newValue, BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - if (property == OrientationProperty) + if (change.Property == OrientationProperty) { - var orientation = newValue.GetValueOrDefault(); + var orientation = change.NewValue.GetValueOrDefault(); //Note: For StackLayout Vertical Orientation means we have a Vertical ScrollOrientation. //Horizontal Orientation means we have a Horizontal ScrollOrientation. diff --git a/src/Avalonia.Layout/UniformGridLayout.cs b/src/Avalonia.Layout/UniformGridLayout.cs index d565fae074..1698f61989 100644 --- a/src/Avalonia.Layout/UniformGridLayout.cs +++ b/src/Avalonia.Layout/UniformGridLayout.cs @@ -464,45 +464,44 @@ namespace Avalonia.Layout gridState.ClearElementOnDataSourceChange(context, args); } - protected override void OnPropertyChanged(AvaloniaProperty property, Optional oldValue, BindingValue newValue, BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - if (property == OrientationProperty) + if (change.Property == OrientationProperty) { - var orientation = newValue.GetValueOrDefault(); + var orientation = change.NewValue.GetValueOrDefault(); //Note: For UniformGridLayout Vertical Orientation means we have a Horizontal ScrollOrientation. Horizontal Orientation means we have a Vertical ScrollOrientation. //i.e. the properties are the inverse of each other. var scrollOrientation = (orientation == Orientation.Horizontal) ? ScrollOrientation.Vertical : ScrollOrientation.Horizontal; _orientation.ScrollOrientation = scrollOrientation; } - else if (property == MinColumnSpacingProperty) + else if (change.Property == MinColumnSpacingProperty) { - _minColumnSpacing = newValue.GetValueOrDefault(); + _minColumnSpacing = change.NewValue.GetValueOrDefault(); } - else if (property == MinRowSpacingProperty) + else if (change.Property == MinRowSpacingProperty) { - _minRowSpacing = newValue.GetValueOrDefault(); + _minRowSpacing = change.NewValue.GetValueOrDefault(); } - else if (property == ItemsJustificationProperty) + else if (change.Property == ItemsJustificationProperty) { - _itemsJustification = newValue.GetValueOrDefault(); - ; + _itemsJustification = change.NewValue.GetValueOrDefault(); } - else if (property == ItemsStretchProperty) + else if (change.Property == ItemsStretchProperty) { - _itemsStretch = newValue.GetValueOrDefault(); + _itemsStretch = change.NewValue.GetValueOrDefault(); } - else if (property == MinItemWidthProperty) + else if (change.Property == MinItemWidthProperty) { - _minItemWidth = newValue.GetValueOrDefault(); + _minItemWidth = change.NewValue.GetValueOrDefault(); } - else if (property == MinItemHeightProperty) + else if (change.Property == MinItemHeightProperty) { - _minItemHeight = newValue.GetValueOrDefault(); + _minItemHeight = change.NewValue.GetValueOrDefault(); } - else if (property == MaximumRowsOrColumnsProperty) + else if (change.Property == MaximumRowsOrColumnsProperty) { - _maximumRowsOrColumns = newValue.GetValueOrDefault(); + _maximumRowsOrColumns = change.NewValue.GetValueOrDefault(); } InvalidateLayout(); diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index b7eec51c85..e4ee293757 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -43,6 +43,11 @@ namespace Avalonia.Native _parent = parent; } + public void GotInputWhenDisabled() + { + // NOP on Popup + } + bool IAvnWindowEvents.Closing() { return true; diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 03a7b4fceb..e91445000a 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -40,7 +40,7 @@ namespace Avalonia.Native bool IAvnWindowEvents.Closing() { - if(_parent.Closing != null) + if (_parent.Closing != null) { return _parent.Closing(); } @@ -52,15 +52,15 @@ namespace Avalonia.Native { _parent.WindowStateChanged?.Invoke((WindowState)state); } + + void IAvnWindowEvents.GotInputWhenDisabled() + { + _parent.GotInputWhenDisabled?.Invoke(); + } } public IAvnWindow Native => _native; - public void ShowDialog(IWindowImpl window) - { - _native.ShowDialog(((WindowImpl)window).Native); - } - public void CanResize(bool value) { _native.CanResize = value; @@ -71,7 +71,7 @@ namespace Avalonia.Native _native.Decorations = (Interop.SystemDecorations)enabled; } - public void SetTitleBarColor (Avalonia.Media.Color color) + public void SetTitleBarColor(Avalonia.Media.Color color) { _native.SetTitleBarColor(new AvnColor { Alpha = color.A, Red = color.R, Green = color.G, Blue = color.B }); } @@ -116,5 +116,17 @@ namespace Avalonia.Native public override IPopupImpl CreatePopup() => _opts.OverlayPopups ? null : new PopupImpl(_factory, _opts, _glFeature, this); + + public Action GotInputWhenDisabled { get; set; } + + public void SetParent(IWindowImpl parent) + { + _native.SetParent(((WindowImpl)parent).Native); + } + + public void SetEnabled(bool enable) + { + _native.SetEnabled(enable); + } } } diff --git a/src/Avalonia.Styling/Styling/PropertySetterBindingInstance.cs b/src/Avalonia.Styling/Styling/PropertySetterBindingInstance.cs index 1400bc2ac3..f975862892 100644 --- a/src/Avalonia.Styling/Styling/PropertySetterBindingInstance.cs +++ b/src/Avalonia.Styling/Styling/PropertySetterBindingInstance.cs @@ -160,7 +160,7 @@ namespace Avalonia.Styling private void ConvertAndPublishNext(object? value) { - _value = value is T v ? v : BindingValue.FromUntyped(value).Convert(); + _value = value is T v ? v : BindingValue.FromUntyped(value); if (_isActive) { diff --git a/src/Avalonia.Visuals/Media/DrawingImage.cs b/src/Avalonia.Visuals/Media/DrawingImage.cs index 57939bab24..56c883014a 100644 --- a/src/Avalonia.Visuals/Media/DrawingImage.cs +++ b/src/Avalonia.Visuals/Media/DrawingImage.cs @@ -63,15 +63,11 @@ namespace Avalonia.Media } /// - protected override void OnPropertyChanged( - AvaloniaProperty property, - Optional oldValue, - BindingValue newValue, - BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - base.OnPropertyChanged(property, oldValue, newValue, priority); + base.OnPropertyChanged(change); - if (property == DrawingProperty) + if (change.Property == DrawingProperty) { RaiseInvalidated(EventArgs.Empty); } diff --git a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs index 42ee35133e..86e2700c04 100644 --- a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs @@ -11,6 +11,46 @@ namespace Avalonia.Media.Imaging /// public class Bitmap : IBitmap { + /// + /// Loads a Bitmap from a stream and decodes at the desired width. Aspect ratio is maintained. + /// This is more efficient than loading and then resizing. + /// + /// The stream to read the bitmap from. This can be any supported image format. + /// The desired width of the resulting bitmap. + /// The to use should any scaling be required. + /// An instance of the class. + public static Bitmap DecodeToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); + return new Bitmap(factory.LoadBitmapToWidth(stream, width, interpolationMode)); + } + + /// + /// Loads a Bitmap from a stream and decodes at the desired height. Aspect ratio is maintained. + /// This is more efficient than loading and then resizing. + /// + /// The stream to read the bitmap from. This can be any supported image format. + /// The desired height of the resulting bitmap. + /// The to use should any scaling be required. + /// An instance of the class. + public static Bitmap DecodeToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); + return new Bitmap(factory.LoadBitmapToHeight(stream, height, interpolationMode)); + } + + /// + /// Creates a Bitmap scaled to a specified size from the current bitmap. + /// + /// The destination size. + /// The to use should any scaling be required. + /// An instance of the class. + public Bitmap CreateScaledBitmap(PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); + return new Bitmap(factory.ResizeBitmap(PlatformImpl.Item, destinationSize, interpolationMode)); + } + /// /// Initializes a new instance of the class. /// @@ -39,7 +79,7 @@ namespace Avalonia.Media.Imaging { PlatformImpl = impl.Clone(); } - + /// /// Initializes a new instance of the class. /// @@ -48,7 +88,7 @@ namespace Avalonia.Media.Imaging { PlatformImpl = RefCountable.Create(impl); } - + /// public virtual void Dispose() { diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index a0102a0f33..ba30272b7b 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.IO; using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Platform { @@ -87,17 +89,37 @@ namespace Avalonia.Platform /// /// Loads a bitmap implementation from a file.. /// - /// The filename of the bitmap. + /// The filename of the bitmap. /// An . IBitmapImpl LoadBitmap(string fileName); /// /// Loads a bitmap implementation from a file.. /// - /// The stream to read the bitmap from. + /// The stream to read the bitmap from. /// An . IBitmapImpl LoadBitmap(Stream stream); + /// + /// Loads a bitmap implementation from a stream to a specified width maintaining aspect ratio. + /// + /// The stream to read the bitmap from. + /// The desired width of the resulting bitmap. + /// The to use should resizing be required. + /// An . + IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); + + /// + /// Loads a bitmap implementation from a stream to a specified height maintaining aspect ratio. + /// + /// The stream to read the bitmap from. + /// The desired height of the resulting bitmap. + /// The to use should resizing be required. + /// An . + IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); + + IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); + /// /// Loads a bitmap implementation from a pixels in memory. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index b8658a7a26..6ad71ac111 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -364,7 +364,7 @@ namespace Avalonia.Rendering.SceneGraph public int DrawOperationIndex { get; } } - private void Add(IDrawOperation node) + private void Add(T node) where T : class, IDrawOperation { using (var refCounted = RefCountable.Create(node)) { diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 1b3d1a7dda..643b037b3f 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -41,9 +41,9 @@ namespace Avalonia.X11 private IntPtr _renderHandle; private bool _mapped; private bool _wasMappedAtLeastOnce = false; - private HashSet _transientChildren = new HashSet(); - private X11Window _transientParent; private double? _scalingOverride; + private bool _disabled; + public object SyncRoot { get; } = new object(); class InputEventContainer @@ -746,7 +746,6 @@ namespace Avalonia.X11 void Cleanup() { - SetTransientParent(null, false); if (_xic != IntPtr.Zero) { XDestroyIC(_xic); @@ -773,38 +772,24 @@ namespace Avalonia.X11 bool ActivateTransientChildIfNeeded() { - if (_transientChildren.Count == 0) - return false; - var child = _transientChildren.First(); - if (!child.ActivateTransientChildIfNeeded()) - child.Activate(); - return true; - } - - void SetTransientParent(X11Window window, bool informServer = true) - { - _transientParent?._transientChildren.Remove(this); - _transientParent = window; - _transientParent?._transientChildren.Add(this); - if (informServer) - SetTransientForHint(_transientParent?._handle); + if (_disabled) + { + GotInputWhenDisabled?.Invoke(); + return true; + } + + return false; } - void SetTransientForHint(IntPtr? parent) + public void SetParent(IWindowImpl parent) { - if (parent == null || parent == IntPtr.Zero) + if (parent == null || parent.Handle == null || parent.Handle.Handle == IntPtr.Zero) XDeleteProperty(_x11.Display, _handle, _x11.Atoms.XA_WM_TRANSIENT_FOR); else - XSetTransientForHint(_x11.Display, _handle, parent.Value); + XSetTransientForHint(_x11.Display, _handle, parent.Handle.Handle); } public void Show() - { - SetTransientParent(null); - ShowCore(); - } - - void ShowCore() { _wasMappedAtLeastOnce = true; XMapWindow(_x11.Display, _handle); @@ -813,7 +798,6 @@ namespace Avalonia.X11 public void Hide() => XUnmapWindow(_x11.Display, _handle); - public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling); public PixelPoint PointToScreen(Point point) => new PixelPoint( @@ -1034,13 +1018,14 @@ namespace Avalonia.X11 { ChangeWMAtoms(value, _x11.Atoms._NET_WM_STATE_ABOVE); } - - public void ShowDialog(IWindowImpl parent) + + public void SetEnabled(bool enable) { - SetTransientParent((X11Window)parent); - ShowCore(); + _disabled = !enable; } + public Action GotInputWhenDisabled { get; set; } + public void SetIcon(IWindowIconImpl icon) { if (icon != null) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 44bbf0590a..26fdb08a4b 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -124,29 +124,12 @@ namespace Avalonia.Skia Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity)) }) { - paint.FilterQuality = GetInterpolationMode(bitmapInterpolationMode); + paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality(); drawableImage.Draw(this, s, d, paint); } } - private static SKFilterQuality GetInterpolationMode(BitmapInterpolationMode interpolationMode) - { - switch (interpolationMode) - { - case BitmapInterpolationMode.LowQuality: - return SKFilterQuality.Low; - case BitmapInterpolationMode.MediumQuality: - return SKFilterQuality.Medium; - case BitmapInterpolationMode.HighQuality: - return SKFilterQuality.High; - case BitmapInterpolationMode.Default: - return SKFilterQuality.None; - default: - throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null); - } - } - /// public void DrawBitmap(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index 9222c5ac61..e84c7e34de 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -1,7 +1,11 @@ using System; using System.IO; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Skia.Helpers; +using Avalonia.Visuals.Media.Imaging; using SkiaSharp; namespace Avalonia.Skia @@ -36,6 +40,75 @@ namespace Avalonia.Skia } } + public ImmutableBitmap(ImmutableBitmap src, PixelSize destinationSize, BitmapInterpolationMode interpolationMode) + { + SKImageInfo info = new SKImageInfo(destinationSize.Width, destinationSize.Height, SKColorType.Bgra8888); + SKImage output = SKImage.Create(info); + src._image.ScalePixels(output.PeekPixels(), interpolationMode.ToSKFilterQuality()); + + _image = output; + + PixelSize = new PixelSize(_image.Width, _image.Height); + + // TODO: Skia doesn't have an API for DPI. + Dpi = new Vector(96, 96); + } + + public ImmutableBitmap(Stream stream, int decodeSize, bool horizontal, BitmapInterpolationMode interpolationMode) + { + using (var skStream = new SKManagedStream(stream)) + using (var codec = SKCodec.Create(skStream)) + { + var info = codec.Info; + + // get the scale that is nearest to what we want (eg: jpg returned 512) + var supportedScale = codec.GetScaledDimensions(horizontal ? ((float)decodeSize / info.Width) : ((float)decodeSize / info.Height)); + + // decode the bitmap at the nearest size + var nearest = new SKImageInfo(supportedScale.Width, supportedScale.Height); + var bmp = SKBitmap.Decode(codec, nearest); + + // now scale that to the size that we want + var realScale = horizontal ? ((double)info.Height / info.Width) : ((double)info.Width / info.Height); + + SKImageInfo desired; + + + if (horizontal) + { + desired = new SKImageInfo(decodeSize, (int)(realScale * decodeSize)); + } + else + { + desired = new SKImageInfo((int)(realScale * decodeSize), decodeSize); + } + + if (bmp.Width != desired.Width || bmp.Height != desired.Height) + { + if (bmp.Height != bmp.Width) + { + + } + var scaledBmp = bmp.Resize(desired, interpolationMode.ToSKFilterQuality()); + bmp.Dispose(); + bmp = scaledBmp; + } + + _image = SKImage.FromBitmap(bmp); + bmp.Dispose(); + + if (_image == null) + { + throw new ArgumentException("Unable to load bitmap from provided data"); + } + + PixelSize = new PixelSize(_image.Width, _image.Height); + + // TODO: Skia doesn't have an API for DPI. + Dpi = new Vector(96, 96); + } + } + /// /// Create immutable bitmap from given pixel data copy. /// diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 2fee282860..0bc5dd56ac 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -1,13 +1,16 @@ using System; using System.Collections; using System.Collections.Generic; -using System.IO; +using System.IO; +using System.Security.Cryptography; using System.Linq; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Media; +using Avalonia.Media.Imaging; using Avalonia.OpenGL; using Avalonia.OpenGL.Imaging; using Avalonia.Platform; +using Avalonia.Visuals.Media.Imaging; using SkiaSharp; namespace Avalonia.Skia @@ -57,12 +60,6 @@ namespace Avalonia.Skia return new StreamGeometryImpl(); } - /// - public IBitmapImpl LoadBitmap(Stream stream) - { - return new ImmutableBitmap(stream); - } - /// public IBitmapImpl LoadBitmap(string fileName) { @@ -72,12 +69,43 @@ namespace Avalonia.Skia } } + /// + public IBitmapImpl LoadBitmap(Stream stream) + { + return new ImmutableBitmap(stream); + } + /// public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) { return new ImmutableBitmap(size, dpi, stride, format, data); } + /// + public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new ImmutableBitmap(stream, width, true, interpolationMode); + } + + /// + public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new ImmutableBitmap(stream, height, false, interpolationMode); + } + + /// + public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + if (bitmapImpl is ImmutableBitmap ibmp) + { + return new ImmutableBitmap(ibmp, destinationSize, interpolationMode); + } + else + { + throw new Exception("Invalid source bitmap type."); + } + } + /// public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi) { diff --git a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs index 459486f784..6375f74c59 100644 --- a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs +++ b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs @@ -1,12 +1,30 @@ using System; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Visuals.Media.Imaging; using SkiaSharp; namespace Avalonia.Skia { public static class SkiaSharpExtensions { + public static SKFilterQuality ToSKFilterQuality(this BitmapInterpolationMode interpolationMode) + { + switch (interpolationMode) + { + case BitmapInterpolationMode.LowQuality: + return SKFilterQuality.Low; + case BitmapInterpolationMode.MediumQuality: + return SKFilterQuality.Medium; + case BitmapInterpolationMode.HighQuality: + return SKFilterQuality.High; + case BitmapInterpolationMode.Default: + return SKFilterQuality.None; + default: + throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null); + } + } + public static SKPoint ToSKPoint(this Point p) { return new SKPoint((float)p.X, (float)p.Y); diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 96bd96341c..c4c0541d53 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -7,7 +7,9 @@ using Avalonia.Controls.Platform.Surfaces; using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Media; +using Avalonia.Media.Imaging; using Avalonia.Platform; +using Avalonia.Visuals.Media.Imaging; using SharpDX.DirectWrite; using GlyphRun = Avalonia.Media.GlyphRun; using TextAlignment = Avalonia.Media.TextAlignment; @@ -179,16 +181,38 @@ namespace Avalonia.Direct2D1 public IGeometryImpl CreateRectangleGeometry(Rect rect) => new RectangleGeometryImpl(rect); public IStreamGeometryImpl CreateStreamGeometry() => new StreamGeometryImpl(); + /// public IBitmapImpl LoadBitmap(string fileName) { return new WicBitmapImpl(fileName); } + /// public IBitmapImpl LoadBitmap(Stream stream) { return new WicBitmapImpl(stream); } + /// + public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new WicBitmapImpl(stream, width, true, interpolationMode); + } + + /// + public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new WicBitmapImpl(stream, height, false, interpolationMode); + } + + /// + public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + // https://github.com/sharpdx/SharpDX/issues/959 blocks implementation. + throw new NotImplementedException(); + } + + /// public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) { return new WicBitmapImpl(format, data, size, dpi, stride); diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index f5159b1f84..743abddd1e 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Security.Cryptography; using Avalonia.Win32.Interop; using SharpDX.WIC; using APixelFormat = Avalonia.Platform.PixelFormat; @@ -14,6 +15,26 @@ namespace Avalonia.Direct2D1.Media { private BitmapDecoder _decoder; + private static BitmapInterpolationMode ConvertInterpolationMode(Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode interpolationMode) + { + switch (interpolationMode) + { + case Visuals.Media.Imaging.BitmapInterpolationMode.Default: + return BitmapInterpolationMode.Fant; + + case Visuals.Media.Imaging.BitmapInterpolationMode.LowQuality: + return BitmapInterpolationMode.NearestNeighbor; + + case Visuals.Media.Imaging.BitmapInterpolationMode.MediumQuality: + return BitmapInterpolationMode.Fant; + + default: + case Visuals.Media.Imaging.BitmapInterpolationMode.HighQuality: + return BitmapInterpolationMode.HighQualityCubic; + + } + } + /// /// Initializes a new instance of the class. /// @@ -27,6 +48,12 @@ namespace Avalonia.Direct2D1.Media } } + private WicBitmapImpl(Bitmap bmp) + { + WicImpl = bmp; + Dpi = new Vector(96, 96); + } + /// /// Initializes a new instance of the class. /// @@ -60,7 +87,7 @@ namespace Avalonia.Direct2D1.Media size.Height, pixelFormat.Value.ToWic(), BitmapCreateCacheOption.CacheOnLoad); - WicImpl.SetResolution(dpi.X, dpi.Y); + Dpi = dpi; } @@ -84,6 +111,43 @@ namespace Avalonia.Direct2D1.Media } } + public WicBitmapImpl(Stream stream, int decodeSize, bool horizontal, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode interpolationMode) + { + _decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, stream, DecodeOptions.CacheOnLoad); + + var frame = _decoder.GetFrame(0); + + // now scale that to the size that we want + var realScale = horizontal ? ((double)frame.Size.Height / frame.Size.Width) : ((double)frame.Size.Width / frame.Size.Height); + + PixelSize desired; + + if (horizontal) + { + desired = new PixelSize(decodeSize, (int)(realScale * decodeSize)); + } + else + { + desired = new PixelSize((int)(realScale * decodeSize), decodeSize); + } + + if (frame.Size.Width != desired.Width || frame.Size.Height != desired.Height) + { + using (var scaler = new BitmapScaler(Direct2D1Platform.ImagingFactory)) + { + scaler.Initialize(frame, desired.Width, desired.Height, ConvertInterpolationMode(interpolationMode)); + + WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, scaler, BitmapCreateCacheOption.CacheOnLoad); + } + } + else + { + WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, frame, BitmapCreateCacheOption.CacheOnLoad); + } + + Dpi = new Vector(96, 96); + } + public override Vector Dpi { get; } public override PixelSize PixelSize => WicImpl.Size.ToAvalonia(); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs index 3ea8c1e48f..138553b962 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs @@ -77,12 +77,6 @@ namespace Avalonia.Win32 s_instances.Remove(this); Closed?.Invoke(); - if (_parent != null) - { - _parent._disabledBy.Remove(this); - _parent.UpdateEnabled(); - } - _mouseDevice.Dispose(); _touchDevice?.Dispose(); //Free other resources diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 608563cb67..cc4c12ec3c 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -45,7 +45,6 @@ namespace Avalonia.Win32 #endif private const WindowStyles WindowStateMask = (WindowStyles.WS_MAXIMIZE | WindowStyles.WS_MINIMIZE); - private readonly List _disabledBy; private readonly TouchDevice _touchDevice; private readonly MouseDevice _mouseDevice; private readonly ManagedDeferredRendererLock _rendererLock; @@ -70,7 +69,6 @@ namespace Avalonia.Win32 public WindowImpl() { - _disabledBy = new List(); _touchDevice = new TouchDevice(); _mouseDevice = new WindowsMouseDevice(); @@ -342,31 +340,25 @@ namespace Avalonia.Win32 public void Hide() { - if (_parent != null) - { - _parent._disabledBy.Remove(this); - _parent.UpdateEnabled(); - _parent = null; - } - UnmanagedMethods.ShowWindow(_hwnd, ShowWindowCommand.Hide); } public virtual void Show() { - SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, IntPtr.Zero); + SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, _parent != null ? _parent._hwnd : IntPtr.Zero); ShowWindow(_showWindowState); } - public void ShowDialog(IWindowImpl parent) + public Action GotInputWhenDisabled { get; set; } + + public void SetParent(IWindowImpl parent) { _parent = (WindowImpl)parent; - _parent._disabledBy.Add(this); - _parent.UpdateEnabled(); - SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, ((WindowImpl)parent)._hwnd); - ShowWindow(_showWindowState); + SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, _parent._hwnd); } + public void SetEnabled(bool enable) => EnableWindow(_hwnd, enable); + public void BeginMoveDrag(PointerPressedEventArgs e) { _mouseDevice.Capture(null); @@ -666,7 +658,7 @@ namespace Avalonia.Win32 } } - private WindowStyles GetWindowStateStyles () + private WindowStyles GetWindowStateStyles() { return GetStyle() & WindowStateMask; } @@ -721,11 +713,6 @@ namespace Avalonia.Win32 } } - private void UpdateEnabled() - { - EnableWindow(_hwnd, _disabledBy.Count == 0); - } - private void UpdateWindowProperties(WindowProperties newProperties, bool forceChanges = false) { var oldProperties = _windowProperties; diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs index 5da1f95646..6bd29a1577 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs @@ -1,5 +1,6 @@ using System; using System.Reactive.Subjects; +using Avalonia.Data; using Xunit; namespace Avalonia.Base.UnitTests @@ -63,6 +64,56 @@ namespace Avalonia.Base.UnitTests Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); } + [Fact] + public void GetBaseValue_LocalValue_Ignores_Default_Value() + { + var target = new Class3(); + + target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation); + Assert.False(target.GetBaseValue(Class1.FooProperty, BindingPriority.LocalValue).HasValue); + } + + [Fact] + public void GetBaseValue_LocalValue_Returns_Local_Value() + { + var target = new Class3(); + + target.SetValue(Class1.FooProperty, "local"); + target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation); + Assert.Equal("local", target.GetBaseValue(Class1.FooProperty, BindingPriority.LocalValue).Value); + } + + [Fact] + public void GetBaseValue_LocalValue_Returns_Style_Value() + { + var target = new Class3(); + + target.SetValue(Class1.FooProperty, "style", BindingPriority.Style); + target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation); + Assert.Equal("style", target.GetBaseValue(Class1.FooProperty, BindingPriority.LocalValue).Value); + } + + [Fact] + public void GetBaseValue_Style_Ignores_LocalValue_Animated_Value() + { + var target = new Class3(); + + target.Bind(Class1.FooProperty, new BehaviorSubject("animated"), BindingPriority.Animation); + target.SetValue(Class1.FooProperty, "local"); + Assert.False(target.GetBaseValue(Class1.FooProperty, BindingPriority.Style).HasValue); + } + + [Fact] + public void GetBaseValue_Style_Returns_Style_Value() + { + var target = new Class3(); + + target.SetValue(Class1.FooProperty, "local"); + target.SetValue(Class1.FooProperty, "style", BindingPriority.Style); + target.Bind(Class1.FooProperty, new BehaviorSubject("animated"), BindingPriority.Animation); + Assert.Equal("style", target.GetBaseValue(Class1.FooProperty, BindingPriority.Style)); + } + private class Class1 : AvaloniaObject { public static readonly StyledProperty FooProperty = diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs new file mode 100644 index 0000000000..e8fc3f9f40 --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs @@ -0,0 +1,142 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using Avalonia.Data; +using Xunit; + +namespace Avalonia.Base.UnitTests +{ + public class AvaloniaObjectTests_OnPropertyChanged + { + [Fact] + public void OnPropertyChangedCore_Is_Called_On_Property_Change() + { + var target = new Class1(); + + target.SetValue(Class1.FooProperty, "newvalue"); + + Assert.Equal(1, target.CoreChanges.Count); + + var change = (AvaloniaPropertyChangedEventArgs)target.CoreChanges[0]; + + Assert.Equal("newvalue", change.NewValue.Value); + Assert.Equal("foodefault", change.OldValue.Value); + Assert.Equal(BindingPriority.LocalValue, change.Priority); + Assert.True(change.IsEffectiveValueChange); + } + + [Fact] + public void OnPropertyChangedCore_Is_Called_On_Non_Effective_Property_Value_Change() + { + var target = new Class1(); + + target.SetValue(Class1.FooProperty, "newvalue"); + target.SetValue(Class1.FooProperty, "styled", BindingPriority.Style); + + Assert.Equal(2, target.CoreChanges.Count); + + var change = (AvaloniaPropertyChangedEventArgs)target.CoreChanges[1]; + + Assert.Equal("styled", change.NewValue.Value); + Assert.False(change.OldValue.HasValue); + Assert.Equal(BindingPriority.Style, change.Priority); + Assert.False(change.IsEffectiveValueChange); + } + + [Fact] + public void OnPropertyChangedCore_Is_Called_On_All_Binding_Property_Changes() + { + var target = new Class1(); + var style = new Subject>(); + var animation = new Subject>(); + var templatedParent = new Subject>(); + + target.Bind(Class1.FooProperty, style, BindingPriority.Style); + target.Bind(Class1.FooProperty, animation, BindingPriority.Animation); + target.Bind(Class1.FooProperty, templatedParent, BindingPriority.TemplatedParent); + + style.OnNext("style1"); + templatedParent.OnNext("tp1"); + animation.OnNext("a1"); + templatedParent.OnNext("tp2"); + templatedParent.OnCompleted(); + animation.OnNext("a2"); + style.OnNext("style2"); + style.OnCompleted(); + animation.OnCompleted(); + + var changes = target.CoreChanges.Cast>(); + + Assert.Equal( + new[] { true, true, true, false, false, true, false, false, true }, + changes.Select(x => x.IsEffectiveValueChange).ToList()); + Assert.Equal( + new[] { "style1", "tp1", "a1", "tp2", "$unset", "a2", "style2", "$unset", "foodefault" }, + changes.Select(x => x.NewValue.GetValueOrDefault("$unset")).ToList()); + Assert.Equal( + new[] { "foodefault", "style1", "tp1", "$unset", "$unset", "a1", "$unset", "$unset", "a2" }, + changes.Select(x => x.OldValue.GetValueOrDefault("$unset")).ToList()); + } + + [Fact] + public void OnPropertyChanged_Is_Called_Only_For_Effective_Value_Changes() + { + var target = new Class1(); + + target.SetValue(Class1.FooProperty, "newvalue"); + target.SetValue(Class1.FooProperty, "styled", BindingPriority.Style); + + Assert.Equal(1, target.Changes.Count); + Assert.Equal(2, target.CoreChanges.Count); + } + + private class Class1 : AvaloniaObject + { + public static readonly StyledProperty FooProperty = + AvaloniaProperty.Register("Foo", "foodefault"); + + public Class1() + { + Changes = new List(); + CoreChanges = new List(); + } + + public List Changes { get; } + public List CoreChanges { get; } + + protected override void OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change) + { + CoreChanges.Add(Clone(change)); + base.OnPropertyChangedCore(change); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + Changes.Add(Clone(change)); + base.OnPropertyChanged(change); + } + + private static AvaloniaPropertyChangedEventArgs Clone(AvaloniaPropertyChangedEventArgs change) + { + var result = new AvaloniaPropertyChangedEventArgs( + change.Sender, + change.Property, + change.OldValue, + change.NewValue, + change.Priority); + + if (!change.IsEffectiveValueChange) + { + result.MarkNonEffectiveValue(); + } + + return result; + } + } + } +} diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs index 941dd9bd98..d7f927372e 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Avalonia.Data; using Avalonia.Utilities; using Xunit; @@ -88,6 +89,30 @@ namespace Avalonia.Base.UnitTests Assert.Equal("newvalue", value); } + [Fact] + public void Changed_Observable_Fired_Only_On_Effective_Value_Change() + { + var target = new Class1(); + var result = new List(); + + Class1.FooProperty.Changed.Subscribe(x => result.Add((string)x.NewValue)); + target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation); + target.SetValue(Class1.FooProperty, "local"); + + Assert.Equal(new[] { "animated" }, result); + } + + [Fact] + public void Notify_Fired_Only_On_Effective_Value_Change() + { + var target = new Class1(); + + target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation); + target.SetValue(Class1.FooProperty, "local"); + + Assert.Equal(2, target.NotifyCount); + } + [Fact] public void Property_Equals_Should_Handle_Null() { @@ -144,6 +169,11 @@ namespace Avalonia.Base.UnitTests throw new NotImplementedException(); } + internal override object RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority) + { + throw new NotImplementedException(); + } + internal override void RouteInheritanceParentChanged(AvaloniaObject o, IAvaloniaObject oldParent) { throw new NotImplementedException(); @@ -161,7 +191,14 @@ namespace Avalonia.Base.UnitTests private class Class1 : AvaloniaObject { public static readonly StyledProperty FooProperty = - AvaloniaProperty.Register("Foo", "default"); + AvaloniaProperty.Register("Foo", "default", notifying: FooNotifying); + + public int NotifyCount { get; private set; } + + private static void FooNotifying(IAvaloniaObject o, bool n) + { + ++((Class1)o).NotifyCount; + } } private class Class2 : Class1 diff --git a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs b/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs index 5e69b8490d..0caa984a22 100644 --- a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs +++ b/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs @@ -10,7 +10,7 @@ namespace Avalonia.Base.UnitTests { public class PriorityValueTests { - private static readonly IValueSink NullSink = Mock.Of(); + private static readonly IValueSink NullSink = new MockSink(); private static readonly IAvaloniaObject Owner = Mock.Of(); private static readonly StyledProperty TestProperty = new StyledProperty( "Test", @@ -30,8 +30,28 @@ namespace Avalonia.Base.UnitTests BindingPriority.StyleTrigger, NullSink)); - Assert.Equal("1", target.Value.Value); - Assert.Equal(BindingPriority.StyleTrigger, target.ValuePriority); + Assert.Equal("1", target.GetValue().Value); + Assert.Equal(BindingPriority.StyleTrigger, target.Priority); + } + + [Fact] + public void GetValue_Should_Respect_MaxPriority() + { + var target = new PriorityValue( + Owner, + TestProperty, + NullSink); + + target.SetValue("animation", BindingPriority.Animation); + target.SetValue("local", BindingPriority.LocalValue); + target.SetValue("styletrigger", BindingPriority.StyleTrigger); + target.SetValue("style", BindingPriority.Style); + + Assert.Equal("animation", target.GetValue(BindingPriority.Animation)); + Assert.Equal("local", target.GetValue(BindingPriority.LocalValue)); + Assert.Equal("styletrigger", target.GetValue(BindingPriority.StyleTrigger)); + Assert.Equal("style", target.GetValue(BindingPriority.TemplatedParent)); + Assert.Equal("style", target.GetValue(BindingPriority.Style)); } [Fact] @@ -61,12 +81,31 @@ namespace Avalonia.Base.UnitTests var result = target.Entries .OfType>() - .Select(x => x.Value.Value) + .Select(x => x.GetValue().Value) .ToList(); Assert.Equal(new[] { "1", "2" }, result); } + [Fact] + public void Priority_Should_Be_Set() + { + var target = new PriorityValue( + Owner, + TestProperty, + NullSink); + + Assert.Equal(BindingPriority.Unset, target.Priority); + target.SetValue("style", BindingPriority.Style); + Assert.Equal(BindingPriority.Style, target.Priority); + target.SetValue("local", BindingPriority.LocalValue); + Assert.Equal(BindingPriority.LocalValue, target.Priority); + target.SetValue("animation", BindingPriority.Animation); + Assert.Equal(BindingPriority.Animation, target.Priority); + target.SetValue("local2", BindingPriority.LocalValue); + Assert.Equal(BindingPriority.Animation, target.Priority); + } + [Fact] public void Binding_With_Same_Priority_Should_Be_Appended() { @@ -184,7 +223,7 @@ namespace Avalonia.Base.UnitTests target.AddBinding(source2, BindingPriority.Style).Start(); target.AddBinding(source3, BindingPriority.Style).Start(); - Assert.Equal("1", target.Value.Value); + Assert.Equal("1", target.GetValue().Value); } [Fact] @@ -196,7 +235,7 @@ namespace Avalonia.Base.UnitTests target.AddBinding(source1, BindingPriority.LocalValue).Start(); target.SetValue("2", BindingPriority.LocalValue); - Assert.Equal("2", target.Value.Value); + Assert.Equal("2", target.GetValue().Value); } [Fact] @@ -208,7 +247,7 @@ namespace Avalonia.Base.UnitTests target.AddBinding(source1, BindingPriority.Style).Start(); target.SetValue("2", BindingPriority.LocalValue); - Assert.Equal("2", target.Value.Value); + Assert.Equal("2", target.GetValue().Value); } [Fact] @@ -220,7 +259,39 @@ namespace Avalonia.Base.UnitTests target.AddBinding(source1, BindingPriority.Animation).Start(); target.SetValue("2", BindingPriority.LocalValue); - Assert.Equal("1", target.Value.Value); + Assert.Equal("1", target.GetValue().Value); + } + + [Fact] + public void NonAnimated_Value_Should_Be_Correct_1() + { + var target = new PriorityValue(Owner, TestProperty, NullSink); + var source1 = new Source("1"); + var source2 = new Source("2"); + var source3 = new Source("3"); + + target.AddBinding(source1, BindingPriority.LocalValue).Start(); + target.AddBinding(source2, BindingPriority.Style).Start(); + target.AddBinding(source3, BindingPriority.Animation).Start(); + + Assert.Equal("3", target.GetValue().Value); + Assert.Equal("1", target.GetValue(BindingPriority.LocalValue).Value); + } + + [Fact] + public void NonAnimated_Value_Should_Be_Correct_2() + { + var target = new PriorityValue(Owner, TestProperty, NullSink); + var source1 = new Source("1"); + var source2 = new Source("2"); + var source3 = new Source("3"); + + target.AddBinding(source1, BindingPriority.Animation).Start(); + target.AddBinding(source2, BindingPriority.Style).Start(); + target.AddBinding(source3, BindingPriority.Style).Start(); + + Assert.Equal("1", target.GetValue().Value); + Assert.Equal("3", target.GetValue(BindingPriority.LocalValue).Value); } private class Source : IObservable> @@ -239,5 +310,16 @@ namespace Avalonia.Base.UnitTests public void OnCompleted() => _observer.OnCompleted(); } + + private class MockSink : IValueSink + { + public void Completed(StyledPropertyBase property, IPriorityValueEntry entry, Optional oldValue) + { + } + + public void ValueChanged(AvaloniaPropertyChangedEventArgs change) + { + } + } } } diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index 5e29893946..d9fb5e03fc 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -4,6 +4,7 @@ using System.IO; using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; +using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Benchmarks { @@ -65,6 +66,21 @@ namespace Avalonia.Benchmarks throw new NotImplementedException(); } + public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + public IFontManagerImpl CreateFontManager() { return new MockFontManagerImpl(); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index e99be9cfd2..80c8a34ffd 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -322,10 +322,9 @@ namespace Avalonia.Controls.UnitTests { var window = new Window(); window.WindowStartupLocation = WindowStartupLocation.CenterOwner; - window.Position = new PixelPoint(60, 40); - window.Owner = parentWindow; + window.Position = new PixelPoint(60, 40); - window.Show(); + window.ShowDialog(parentWindow); var expectedPosition = new PixelPoint( (int)(parentWindow.Position.X + parentWindow.ClientSize.Width / 2 - window.ClientSize.Width / 2), diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs index 3d0cb88c8a..cb37dea220 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs @@ -18,10 +18,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml base.OnAttachedToLogicalTree(e); } - protected override void OnPropertyChanged(AvaloniaProperty property, Optional oldValue, BindingValue newValue, BindingPriority priority) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - Order.Add($"Property {property.Name} Changed"); - base.OnPropertyChanged(property, oldValue, newValue, priority); + Order.Add($"Property {change.Property.Name} Changed"); + base.OnPropertyChanged(change); } void ISupportInitialize.BeginInit() diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index afdf95430b..5f2c6d1a69 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Visuals.Media.Imaging; using Moq; namespace Avalonia.UnitTests @@ -69,6 +70,21 @@ namespace Avalonia.UnitTests return Mock.Of(); } + public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return Mock.Of(); + } + + public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return Mock.Of(); + } + + public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return Mock.Of(); + } + public IBitmapImpl LoadBitmap( PixelFormat format, IntPtr data, diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs index ab30d91971..52552f0bee 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs @@ -36,7 +36,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } } - [Fact] + [Fact(Skip = "https://github.com/moq/moq4/issues/988")] public void AddDirty_With_RenderTransform_Call_RenderRoot_Invalidate() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) @@ -59,7 +59,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } } - [Fact] + [Fact(Skip = "https://github.com/moq/moq4/issues/988")] public void AddDirty_For_Child_Moved_Should_Invalidate_Previous_Bounds() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) @@ -111,7 +111,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } } - [Fact] + [Fact(Skip = "https://github.com/moq/moq4/issues/988")] public void Should_Render_Child_In_Parent_With_RenderTransform() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) @@ -145,7 +145,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } } - [Fact] + [Fact(Skip = "https://github.com/moq/moq4/issues/988")] public void Should_Render_Child_In_Parent_With_RenderTransform2() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 019eefe1a6..558a9a5968 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -4,6 +4,7 @@ using System.IO; using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; +using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Visuals.UnitTests.VisualTree { @@ -83,6 +84,21 @@ namespace Avalonia.Visuals.UnitTests.VisualTree throw new NotImplementedException(); } + public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + class MockStreamGeometry : IStreamGeometryImpl { private MockStreamGeometryContext _impl = new MockStreamGeometryContext();