Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion samples/SampleApp/DemoPages/CalendarDatePickerDemo.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
RowSpacing="30">
<Grid Grid.Column="0" Grid.Row="0"
ColumnDefinitions="Auto, 250"
RowDefinitions="Auto, Auto, Auto, Auto, Auto, Auto, Auto"
RowDefinitions="Auto, Auto, Auto, Auto, Auto, Auto, Auto, Auto"
ColumnSpacing="15"
RowSpacing="14">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Placeholder" VerticalAlignment="Center" />
Expand All @@ -37,6 +37,7 @@
<TextBlock Grid.Row="4" Grid.Column="0" Text="Custom Format" VerticalAlignment="Center" />
<TextBlock Grid.Row="5" Grid.Column="0" Text="Reset" VerticalAlignment="Center" />
<TextBlock Grid.Row="6" Grid.Column="0" Text="Error" VerticalAlignment="Center" />
<TextBlock Grid.Row="7" Grid.Column="0" Text="Inner slots" VerticalAlignment="Center" />

<CalendarDatePicker Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left" />
<CalendarDatePicker Grid.Row="1" Grid.Column="1" Name="DatePicker" HorizontalAlignment="Left" />
Expand All @@ -58,6 +59,20 @@
</system:Exception>
</DataValidationErrors.Error>
</CalendarDatePicker>
<StackPanel Grid.Row="7" Grid.Column="1" Orientation="Horizontal" Spacing="10">
Comment thread
xfortin-devolutions marked this conversation as resolved.
<CalendarDatePicker HorizontalAlignment="Left">
<CalendarDatePickerContent.InnerLeftContent>
<Button Command="{Binding GreenCommand}"><Border Width="8" Height="8" Background="Green" /></Button>
Comment thread
xfortin-devolutions marked this conversation as resolved.
</CalendarDatePickerContent.InnerLeftContent>
<CalendarDatePickerContent.InnerLeftOfCalendarButtonContent>
<Button Command="{Binding OrangeCommand}"><Border Width="8" Height="8" Background="Orange" /></Button>
</CalendarDatePickerContent.InnerLeftOfCalendarButtonContent>
<CalendarDatePickerContent.InnerRightContent>
<Button Command="{Binding RedCommand}"><Border Width="8" Height="8" Background="Red" /></Button>
</CalendarDatePickerContent.InnerRightContent>
</CalendarDatePicker>
<Border Width="12" Height="12" VerticalAlignment="Center" Background="{Binding SelectedBrush}" />
</StackPanel>
</Grid>

<StackPanel Grid.Row="0" Grid.Column="1"
Expand Down
13 changes: 13 additions & 0 deletions samples/SampleApp/ViewModels/CalendarDatePickerViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System;
using System.Collections.ObjectModel;
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

Expand All @@ -15,4 +16,16 @@ private void SetToToday()
{
this.SelectedDate = DateTime.Today;
}

[ObservableProperty]
private IBrush selectedBrush = Brushes.Transparent;

[RelayCommand]
private void Green() => this.SelectedBrush = Brushes.Green;

[RelayCommand]
private void Orange() => this.SelectedBrush = Brushes.Orange;

[RelayCommand]
private void Red() => this.SelectedBrush = Brushes.Red;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
namespace Devolutions.AvaloniaControls.AttachedProperties;

using System.Collections;
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;

public static class CalendarDatePickerContent
{
public static readonly AttachedProperty<IEnumerable> InnerLeftContentProperty =
AvaloniaProperty.RegisterAttached<CalendarDatePicker, IEnumerable>("InnerLeftContent", typeof(CalendarDatePickerContent));

public static readonly AttachedProperty<IEnumerable> InnerLeftOfCalendarButtonContentProperty =
AvaloniaProperty.RegisterAttached<CalendarDatePicker, IEnumerable>("InnerLeftOfCalendarButtonContent", typeof(CalendarDatePickerContent));

public static readonly AttachedProperty<IEnumerable> InnerRightContentProperty =
AvaloniaProperty.RegisterAttached<CalendarDatePicker, IEnumerable>("InnerRightContent", typeof(CalendarDatePickerContent));

public static IEnumerable GetInnerLeftContent(CalendarDatePicker element) => GetOrCreate(element, InnerLeftContentProperty);

public static void SetInnerLeftContent(CalendarDatePicker element, IEnumerable value) => element.SetValue(InnerLeftContentProperty, value);

public static IEnumerable GetInnerLeftOfCalendarButtonContent(CalendarDatePicker element) =>
GetOrCreate(element, InnerLeftOfCalendarButtonContentProperty);

public static void SetInnerLeftOfCalendarButtonContent(CalendarDatePicker element, IEnumerable value) =>
element.SetValue(InnerLeftOfCalendarButtonContentProperty, value);

public static IEnumerable GetInnerRightContent(CalendarDatePicker element) => GetOrCreate(element, InnerRightContentProperty);

public static void SetInnerRightContent(CalendarDatePicker element, IEnumerable value) => element.SetValue(InnerRightContentProperty, value);

// Attached-property defaults are shared across all instances, which is not what we want for
// mutable collections. Lazily create a per-instance AvaloniaList<Control> on first read so
// XAML element-syntax (<CalendarDatePickerContent.InnerLeftContent>...</...>) can Add into it.
private static IEnumerable GetOrCreate(CalendarDatePicker element, AttachedProperty<IEnumerable> property)
{
if (element.GetValue(property) is { } existing)
{
return existing;
}

// ReSharper disable once CollectionNeverUpdated.Local
AvaloniaList<Control> list = [];
element.SetValue(property, list);
return list;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

<x:Double x:Key="CalendarDatePickerCurrentDayFontSize">12</x:Double>
<x:Double x:Key="CalendarDatePickerMinHeight">32</x:Double>
<SolidColorBrush x:Key="CalendarDatePickerGlyphOpenBrush" Color="#939393" />

<ControlTheme x:Key="DatePickerButton"
TargetType="Button">
Expand Down Expand Up @@ -64,9 +65,6 @@
<DataValidationErrors>
<Panel x:Name="LayoutRoot"
HorizontalAlignment="Stretch">
<!-- Brittle Button placement! Placing the Button as TextBox.InnerRightContent breaks functionality,
But this placement breaks if ValidationError text is positioned _above_ the TextBox
Also: centering the button via setting a top margin gives inconsistent results on different monitors -->
<Panel>
<TextBox Name="PART_TextBox"
PlaceholderText="{TemplateBinding PlaceholderText}"
Expand All @@ -76,8 +74,101 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
Padding="{TemplateBinding Padding}">
<TextBox.Styles>
<!-- The slots always assign content to Inner*Content, so these presenters are always
visible and would apply their own padding even when a slot is empty, shifting the
layout vs. a plain picker. Zero it here (an instance Styles collection allows a single
/template/ selector, unlike a ControlTheme); slot spacing comes from the ItemsControls' margins. -->
<Style Selector="TextBox /template/ ContentPresenter#PrefixContent">
<Setter Property="Padding" Value="0" />
</Style>
<Style Selector="TextBox /template/ ContentPresenter#SufixContent">
<Setter Property="Padding" Value="0" />
</Style>
<Style Selector="TextBox /template/ ContentPresenter Button">
<Setter Property="MinHeight" Value="18" />
</Style>
</TextBox.Styles>
<TextBox.InnerLeftContent>
<ItemsControl
VerticalAlignment="Center"
Margin="4 0 0 0"
ItemsSource="{Binding (CalendarDatePickerContent.InnerLeftContent), RelativeSource={RelativeSource AncestorType=CalendarDatePicker}}"
IsVisible="{Binding (CalendarDatePickerContent.InnerLeftContent), RelativeSource={RelativeSource AncestorType=CalendarDatePicker}, Converter={x:Static DevoConverters.IsNullOrEmptyConverter}, ConverterParameter={x:False}}">
Comment thread
xfortin-devolutions marked this conversation as resolved.
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="2" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</TextBox.InnerLeftContent>
<TextBox.InnerRightContent>
<Rectangle Width="22" Fill="Transparent" />
<StackPanel Margin="4 0 0 0" Orientation="Horizontal" Spacing="2" VerticalAlignment="Center">
<ItemsControl
VerticalAlignment="Center"
ItemsSource="{Binding (CalendarDatePickerContent.InnerLeftOfCalendarButtonContent), RelativeSource={RelativeSource AncestorType=CalendarDatePicker}}"
IsVisible="{Binding (CalendarDatePickerContent.InnerLeftOfCalendarButtonContent), RelativeSource={RelativeSource AncestorType=CalendarDatePicker}, Converter={x:Static DevoConverters.IsNullOrEmptyConverter}, ConverterParameter={x:False}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="2" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Button Name="PART_Button"
Theme="{StaticResource DatePickerButton}"
Focusable="False"
VerticalAlignment="Center">
<!-- DropDownGlyphBackground / DropDownGlyph live inside PART_TextBox.InnerRightContent,
i.e. outside the CalendarDatePicker template namescope, so styles / TemplateBinding
cannot reach them. Their state colours are bound to the parent via
$parent[CalendarDatePicker] (open / disabled) and to the border's own IsPointerOver
(hover), replacing the pointerover / flyout-open / disabled styles. -->
<Border Name="DropDownGlyphBackground"
Classes.dropdownOpen="{Binding $parent[CalendarDatePicker].IsDropDownOpen}"
Margin="{DynamicResource DropdownButtonMargin}"
Width="{DynamicResource DropdownButtonWidth}"
Height="{DynamicResource DropdownButtonHeight}"
CornerRadius="{DynamicResource ControlCornerRadius}"
HorizontalAlignment="Right"
VerticalAlignment="Center">
<!-- Hover (the border's own :pointerover) and open (a class bound to the parent's
IsDropDownOpen) are styled locally on the Border, because a "/template/" style
from the CalendarDatePicker cannot reach it inside PART_TextBox content. -->
<Border.Styles>
<Style Selector="Border#DropDownGlyphBackground">
<Setter Property="Background" Value="{DynamicResource TransparentBrush}" />
</Style>
<Style Selector="Border#DropDownGlyphBackground:pointerover">
<Setter Property="Background" Value="{DynamicResource ControlBackgroundPointerOverBrush}" />
</Style>
<Style Selector="Border#DropDownGlyphBackground.dropdownOpen">
<Setter Property="Background" Value="{DynamicResource ControlBackgroundSelectedBrush}" />
</Style>
</Border.Styles>
<PathIcon Name="DropDownGlyph"
UseLayoutRounding="False"
IsHitTestVisible="False"
Width="{DynamicResource DropdownButtonIconWidth}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{BindingToggler {Binding $parent[CalendarDatePicker].IsDropDownOpen},
{DynamicResource CalendarDatePickerGlyphOpenBrush},
{DynamicResourceToggler {Binding $parent[CalendarDatePicker].IsEnabled}, ForegroundHighBrush, CalendarDatePickerCalendarGlyphForegroundDisabled}}"
Data="{StaticResource ChevronPath}" />
</Border>
</Button>
<ItemsControl
VerticalAlignment="Center"
Margin="0 0 4 0"
ItemsSource="{Binding (CalendarDatePickerContent.InnerRightContent), RelativeSource={RelativeSource AncestorType=CalendarDatePicker}}"
IsVisible="{Binding (CalendarDatePickerContent.InnerRightContent), RelativeSource={RelativeSource AncestorType=CalendarDatePicker}, Converter={x:Static DevoConverters.IsNullOrEmptyConverter}, ConverterParameter={x:False}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="2" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</TextBox.InnerRightContent>
</TextBox>
<Border
Expand All @@ -86,30 +177,7 @@
BorderThickness="1"
CornerRadius="{DynamicResource ControlCornerRadius}"
IsVisible="False" />
<Panel Height="{DynamicResource TextBasedInputMinHeight}" VerticalAlignment="Top">
<Button Name="PART_Button"
Theme="{StaticResource DatePickerButton}"
Focusable="False">
<Border Name="DropDownGlyphBackground"
DockPanel.Dock="Right"
Background="Transparent"
Margin="{DynamicResource DropdownButtonMargin}"
Width="{DynamicResource DropdownButtonWidth}"
Height="{DynamicResource DropdownButtonHeight}"
CornerRadius="{DynamicResource ControlCornerRadius}"
HorizontalAlignment="Right"
VerticalAlignment="Center">
<PathIcon Name="DropDownGlyph"
UseLayoutRounding="False"
IsHitTestVisible="False"
Width="{DynamicResource DropdownButtonIconWidth}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{TemplateBinding Foreground}"
Data="{StaticResource ChevronPath}" />
</Border>
</Button>
</Panel>

<Popup Name="PART_Popup"
PlacementTarget="{TemplateBinding}"
Placement="BottomEdgeAlignedLeft"
Expand Down Expand Up @@ -139,26 +207,10 @@
</ControlTemplate>
</Setter>

<!-- Disabled State -->
<Style Selector="^:disabled">
<Style Selector="^ /template/ Button#PART_Button PathIcon">
<Setter Property="TextElement.Foreground" Value="{DynamicResource CalendarDatePickerCalendarGlyphForegroundDisabled}" />
</Style>
</Style>

<Style Selector="^ /template/ Border#DropDownGlyphBackground:pointerover">
<Setter Property="Background" Value="{DynamicResource ControlBackgroundPointerOverBrush}" />
</Style>

<Style Selector="^:flyout-open">
<Style Selector="^ /template/ Border#DropDownGlyphBackground">
<Setter Property="Background" Value="{DynamicResource ControlBackgroundSelectedBrush}" />
</Style>
<Style Selector="^ /template/ PathIcon#DropDownGlyph">
<Setter Property="Foreground" Value="#939393" />
</Style>
</Style>

<!-- Chevron hover / open / disabled colours are handled by bindings on DropDownGlyphBackground and
DropDownGlyph in the template (see comment there); a "/template/" style cannot reach them because
they sit inside PART_TextBox.InnerRightContent. -->

<!-- Focused State -->
<Style Selector="^:focus-within:not(CalendarDatePicker:focus) /template/ Border#Background">
<Setter Property="Background" Value="{DynamicResource TextControlBackgroundFocused}" />
Expand Down
23 changes: 12 additions & 11 deletions src/Devolutions.AvaloniaTheme.DevExpress/Controls/TextBox.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,6 @@
Grid.Column="0"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding InnerLeftContent}"
Padding="{Binding
Padding,
RelativeSource={RelativeSource TemplatedParent},
Converter={x:Static DevoConverters.ThicknessExtractor},
ConverterParameter={x:Static ThicknessSubset.Left}
}"
IsVisible="{Binding Content, RelativeSource={RelativeSource Self}, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<ScrollViewer Name="PART_ScrollViewer"
Grid.Column="1"
Expand Down Expand Up @@ -277,11 +271,6 @@
<ContentPresenter Name="SufixContent"
Grid.Column="2"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{Binding
Padding,
RelativeSource={RelativeSource TemplatedParent},
Converter={x:Static DevoConverters.ThicknessToSelectiveThicknessConverter},
ConverterParameter={StaticResource TextBoxSufixPaddingFactors}}"
Content="{TemplateBinding InnerRightContent}"
IsVisible="{Binding Content, RelativeSource={RelativeSource Self}, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
</Grid>
Expand All @@ -290,6 +279,18 @@
</ControlTemplate>
</Setter>

<!-- Inner-content (Prefix/Sufix) padding is applied via style rather than a direct attribute on the
presenters so that an instance can override it locally (e.g. CalendarDatePicker zeroes it because
it always assigns inner content and would otherwise get padding even when a slot is empty). -->
<Style Selector="^ /template/ ContentPresenter#PrefixContent">
<Setter Property="Padding"
Value="{Binding Padding, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static DevoConverters.ThicknessExtractor}, ConverterParameter={x:Static ThicknessSubset.Left}}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#SufixContent">
<Setter Property="Padding"
Value="{Binding Padding, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static DevoConverters.ThicknessToSelectiveThicknessConverter}, ConverterParameter={StaticResource TextBoxSufixPaddingFactors}}" />
</Style>

<Style Selector="^.borderless">
<Setter Property="BorderThickness" Value="0" />
</Style>
Expand Down
Loading