diff --git a/src/Fabulous.Avalonia/ComponentViews/Collections/Carousel.fs b/src/Fabulous.Avalonia/ComponentViews/Collections/Carousel.fs new file mode 100644 index 00000000..ca23362b --- /dev/null +++ b/src/Fabulous.Avalonia/ComponentViews/Collections/Carousel.fs @@ -0,0 +1,20 @@ +namespace Fabulous.Avalonia.Components + +open Fabulous +open Fabulous.Avalonia + +type IFabComponentCarousel = + inherit IFabComponentSelectingItemsControl + inherit IFabCarousel + +[] +module CarouselBuilders = + type Fabulous.Avalonia.Components.View with + + /// Creates a Carousel widget. + /// The items to display. + /// The template to use to render each item. + static member Carousel<'msg, 'itemData, 'itemMarker when 'itemMarker :> IFabComponentControl> + (items: seq<'itemData>, template: 'itemData -> WidgetBuilder<'msg, 'itemMarker>) + = + WidgetHelpers.buildItems<'msg, IFabComponentCarousel, 'itemData, 'itemMarker> Carousel.WidgetKey ItemsControl.ItemsSourceTemplate items template diff --git a/src/Fabulous.Avalonia/ComponentViews/Collections/ListBox.fs b/src/Fabulous.Avalonia/ComponentViews/Collections/ListBox.fs new file mode 100644 index 00000000..f3f46644 --- /dev/null +++ b/src/Fabulous.Avalonia/ComponentViews/Collections/ListBox.fs @@ -0,0 +1,24 @@ +namespace Fabulous.Avalonia.Components + +open Fabulous +open Fabulous.Avalonia + +type IFabComponentListBox = + inherit IFabComponentSelectingItemsControl + inherit IFabListBox + +[] +module ListBoxBuilders = + type Fabulous.Avalonia.Components.View with + + /// Creates a ListBox widget. + /// The items to display. + /// The template to use to render each item. + static member ListBox<'msg, 'itemData, 'itemMarker when 'itemMarker :> IFabControl> + (items: seq<'itemData>, template: 'itemData -> WidgetBuilder<'msg, 'itemMarker>) + = + WidgetHelpers.buildItems<'msg, IFabComponentListBox, 'itemData, 'itemMarker> ListBox.WidgetKey ItemsControl.ItemsSourceTemplate items template + + /// Creates a ListBox widget. + static member ListBox() = + CollectionBuilder<'msg, IFabComponentListBox, IFabComponentListBoxItem>(ListBox.WidgetKey, ComponentItemsControl.Items) \ No newline at end of file diff --git a/src/Fabulous.Avalonia/ComponentViews/Collections/TabControl.fs b/src/Fabulous.Avalonia/ComponentViews/Collections/TabControl.fs new file mode 100644 index 00000000..3705e577 --- /dev/null +++ b/src/Fabulous.Avalonia/ComponentViews/Collections/TabControl.fs @@ -0,0 +1,25 @@ +namespace Fabulous.Avalonia.Components + +open System.Runtime.CompilerServices +open Avalonia.Controls +open Avalonia.Layout +open Fabulous +open Fabulous.Avalonia +open Fabulous.StackAllocatedCollections + +type IFabComponentTabControl = + inherit IFabComponentSelectingItemsControl + inherit IFabTabControl + +[] +module ComponentTabControlBuilders = + type Fabulous.Avalonia.Components.View with + + /// Creates a TabControl widget. + /// The placement of the tab strip. + static member TabControl(placement: Dock) = + CollectionBuilder(TabControl.WidgetKey, ComponentItemsControl.Items, TabControl.TabStripPlacement.WithValue(placement)) + + /// Creates a TabControl widget. + static member TabControl() = + CollectionBuilder(TabControl.WidgetKey, ComponentItemsControl.Items, TabControl.TabStripPlacement.WithValue(Dock.Top)) \ No newline at end of file diff --git a/src/Fabulous.Avalonia/ComponentViews/Collections/TreeView.fs b/src/Fabulous.Avalonia/ComponentViews/Collections/TreeView.fs new file mode 100644 index 00000000..d9d689c3 --- /dev/null +++ b/src/Fabulous.Avalonia/ComponentViews/Collections/TreeView.fs @@ -0,0 +1,50 @@ +namespace Fabulous.Avalonia.Components + +open System.Collections +open System.Runtime.CompilerServices +open Avalonia.Controls +open Fabulous +open Fabulous.Avalonia + +type IFabComponentTreeView = + inherit IFabComponentItemsControl + inherit IFabTreeView + +type ComponentTreeWidgetItems = + { Nodes: IEnumerable + SubNodesFn: obj -> IEnumerable + Template: obj -> Widget } + +module ComponentTreeView = + let SelectionChanged = + ComponentAttributes.defineEvent "TreeView_SelectionChanged" (fun target -> (target :?> TreeView).SelectionChanged) + +[] +module ComponentTreeViewBuilders = + type Fabulous.Avalonia.Components.View with + + /// Creates a TreeView widget. + /// The root nodes used to populate the TreeView. + /// The sub nodes used to populate the children of each node. + /// The template used to render each node. + static member TreeView<'msg, 'itemData, 'itemMarker when 'itemMarker :> IFabComponentControl> + (nodes: seq<'itemData>, subNodes: 'itemData -> seq<'itemData>, template: 'itemData -> WidgetBuilder) + = + let template (item: obj) = + let item = unbox<'itemData> item + (template item).Compile() + + let data: TreeWidgetItems = + { Nodes = nodes + SubNodesFn = (fun subNode -> subNodes(unbox subNode) :> IEnumerable) + Template = template } + + WidgetBuilder<'msg, IFabComponentTreeView>(TreeView.WidgetKey, TreeView.ItemsSource.WithValue(data)) + +type ComponentTreeViewModifiers = + /// Listens to the TreeView SelectionChanged event. + /// Current widget. + /// Raised when the TreeView SelectionChanged event is fired. + [] + static member inline onSelectionChanged(this: WidgetBuilder, fn: SelectionChangedEventArgs -> unit) = + this.AddScalar(ComponentTreeView.SelectionChanged.WithValue(fn)) diff --git a/src/Fabulous.Avalonia/ComponentViews/Controls/AutoCompleteBox.fs b/src/Fabulous.Avalonia/ComponentViews/Controls/AutoCompleteBox.fs new file mode 100644 index 00000000..93a5ce05 --- /dev/null +++ b/src/Fabulous.Avalonia/ComponentViews/Controls/AutoCompleteBox.fs @@ -0,0 +1,237 @@ +namespace Fabulous.Avalonia.Components + +open System +open System.Runtime.CompilerServices +open System.Threading +open System.Threading.Tasks +open Avalonia.Controls +open Fabulous + +type IFabAutoCompleteBox = + inherit IFabTemplatedControl + +module AutoCompleteBox = + let WidgetKey = Widgets.register() + + let Watermark = + Attributes.defineAvaloniaPropertyWithEquality AutoCompleteBox.WatermarkProperty + + let MinimumPrefixLength = + Attributes.defineAvaloniaPropertyWithEquality AutoCompleteBox.MinimumPrefixLengthProperty + + let MinimumPopulateDelay = + Attributes.defineAvaloniaPropertyWithEquality AutoCompleteBox.MinimumPopulateDelayProperty + + let MaxDropDownHeight = + Attributes.defineAvaloniaPropertyWithEquality AutoCompleteBox.MaxDropDownHeightProperty + + let IsTextCompletionEnabled = + Attributes.defineAvaloniaPropertyWithEquality AutoCompleteBox.IsTextCompletionEnabledProperty + + let FilterMode = + Attributes.defineAvaloniaPropertyWithEquality AutoCompleteBox.FilterModeProperty + + let ItemFilter = + Attributes.defineAvaloniaPropertyWithEquality AutoCompleteBox.ItemFilterProperty + + let TextFilter = + Attributes.defineAvaloniaPropertyWithEquality AutoCompleteBox.TextFilterProperty + + let ItemSelector = + Attributes.defineAvaloniaPropertyWithEquality AutoCompleteBox.ItemSelectorProperty + + let TextSelector = + Attributes.defineAvaloniaPropertyWithEquality AutoCompleteBox.TextSelectorProperty + + let ItemsSource = + Attributes.defineAvaloniaPropertyWithEquality AutoCompleteBox.ItemsSourceProperty + + let AsyncPopulator = + Attributes.defineAvaloniaPropertyWithEquality AutoCompleteBox.AsyncPopulatorProperty + + let Text = + Attributes.defineAvaloniaPropertyWithChangedEvent' "AutoCompleteBox_TextChanged" AutoCompleteBox.TextProperty + + let Populating = + Attributes.defineEvent "AutoCompleteBox_Populating" (fun target -> (target :?> AutoCompleteBox).Populating) + + let Populated = + Attributes.defineEvent "AutoCompleteBox_Populated" (fun target -> (target :?> AutoCompleteBox).Populated) + + let DropDownOpened = + Attributes.defineAvaloniaPropertyWithChangedEvent' "AutoCompleteBox_onDropDownOpened" AutoCompleteBox.IsDropDownOpenProperty + + let SelectionChanged = + Attributes.defineEvent "AutoCompleteBox_SelectionChanged" (fun target -> (target :?> AutoCompleteBox).SelectionChanged) + + /// Allows multi-binding the ValueMemberBinding on an AutoCompleteBox + let MultiValueBinding = + Attributes.defineSimpleScalar + "AutoCompleteBox_MultiValueBinding" + ScalarAttributeComparers.equalityCompare + (fun _ newValueOpt node -> + if newValueOpt.IsSome then + let format, propertyNames = newValueOpt.Value + let target = node.Target :?> AutoCompleteBox + + let rec bindAndCleanUp _ _ = + target.multiBind((fun (box: AutoCompleteBox) -> box.ValueMemberBinding), format, propertyNames) + target.Loaded.RemoveHandler(bindAndCleanUp) // to clean up + + target.Loaded.AddHandler(bindAndCleanUp)) + + /// Allows setting the ItemTemplate on an AutoCompleteBox + let ItemTemplate = + Attributes.defineSimpleScalar Widget> "AutoCompleteBox_ItemTemplate" ScalarAttributeComparers.physicalEqualityCompare (fun _ newValueOpt node -> + let autoComplete = node.Target :?> AutoCompleteBox + + match newValueOpt with + | ValueNone -> autoComplete.ClearValue(AutoCompleteBox.ItemTemplateProperty) + | ValueSome template -> + autoComplete.SetValue(AutoCompleteBox.ItemTemplateProperty, WidgetDataTemplate(node, template)) + |> ignore) + +[] +module AutoCompleteBoxBuilders = + type Fabulous.Avalonia.View with + + /// Creates an AutoCompleteBox widget. + /// The items to display. + static member AutoCompleteBox(items: seq<_>) = + WidgetBuilder<'msg, IFabAutoCompleteBox>(AutoCompleteBox.WidgetKey, AutoCompleteBox.ItemsSource.WithValue(items)) + + /// Creates an AutoCompleteBox widget. + /// The function to populate the items. + static member AutoCompleteBox(populator: string -> CancellationToken -> Task>) = + WidgetBuilder<'msg, IFabAutoCompleteBox>(AutoCompleteBox.WidgetKey, AutoCompleteBox.AsyncPopulator.WithValue(populator)) + +type AutoCompleteBoxModifiers = + /// Sets the MinimumPrefixLength property. + /// Current widget. + /// The MinimumPrefixLength value. + [] + static member inline minimumPrefixLength(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, value: int) = + this.AddScalar(AutoCompleteBox.MinimumPrefixLength.WithValue(value)) + + /// Sets the MinimumPopulateDelay property. + /// Current widget. + /// The MinimumPopulateDelay value. + [] + static member inline minimumPopulateDelay(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, value: TimeSpan) = + this.AddScalar(AutoCompleteBox.MinimumPopulateDelay.WithValue(value)) + + /// Sets the MaxDropDownHeight property. + /// Current widget. + /// The MaxDropDownHeight value. + [] + static member inline maxDropDownHeight(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, value: float) = + this.AddScalar(AutoCompleteBox.MaxDropDownHeight.WithValue(value)) + + /// Sets the IsTextCompletionEnabled property. + /// Current widget. + /// The IsTextCompletionEnabled value. + [] + static member inline isTextCompletionEnabled(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, value: bool) = + this.AddScalar(AutoCompleteBox.IsTextCompletionEnabled.WithValue(value)) + + /// Sets the Watermark property. + /// Current widget. + /// The Watermark value. + [] + static member inline watermark(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, value: string) = + this.AddScalar(AutoCompleteBox.Watermark.WithValue(value)) + + /// Sets the FilterMode property. + /// Current widget. + /// The FilterMode value. + [] + static member inline filterMode(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, value: AutoCompleteFilterMode) = + this.AddScalar(AutoCompleteBox.FilterMode.WithValue(value)) + + /// Sets the ItemFilter property. + /// Current widget. + /// The ItemFilter value. + [] + static member inline itemFilter(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, fn: string -> obj -> bool) = + this.AddScalar(AutoCompleteBox.ItemFilter.WithValue(fn)) + + /// Sets the ItemTemplate property. + /// Current widget. + /// The template to render the items with. + [] + static member inline itemTemplate(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, template: 'item -> WidgetBuilder<'msg, #IFabControl>) = + this.AddScalar(AutoCompleteBox.ItemTemplate.WithValue(WidgetHelpers.compileTemplate template)) + + /// Sets the TextFilter property. + /// Current widget. + /// The TextFilter value. + [] + static member inline textFilter(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, value: string -> string -> bool) = + this.AddScalar(AutoCompleteBox.TextFilter.WithValue(value)) + + /// Binds the AutoCompleteBox.TextProperty. + /// Current widget. + /// The value to bind. + /// A function mapping the updated text to a 'msg to raise on user change. + [] + static member inline onTextChanged(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, value: string, fn: string -> 'msg) = + this.AddScalar(AutoCompleteBox.Text.WithValue(ValueEventData.create value fn)) + + /// Sets the AutoCompleteBox.ItemSelector property to a custom method that combines the user-entered text + /// and the selected item to return the new text input value. + /// Current widget. + /// A custom method receiving the entered text as the first + /// and the selected item as the second argument and returns the updated text of the input after selection. + [] + static member inline itemSelector(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, value: string -> obj -> string) = + this.AddScalar(AutoCompleteBox.ItemSelector.WithValue(value)) + + /// Sets the AutoCompleteBox.TextSelector property to a custom method that combines the user-entered text + /// and the selected item's text to return the new text input value. + /// Current widget. + /// A custom method receiving the entered text as the first + /// and the text of the selected item as the second argument and returns the updated text of the input after selection. + [] + static member inline textSelector(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, value: string -> string -> string) = + this.AddScalar(AutoCompleteBox.TextSelector.WithValue(value)) + + [] + static member inline onPopulating(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, fn: PopulatingEventArgs -> 'msg) = + this.AddScalar(AutoCompleteBox.Populating.WithValue(fn)) + + /// Listens to the AutoCompleteBox Populated event. + /// Current widget. + /// Raised when the AutoCompleteBox Populated event is fired. + [] + static member inline onPopulated(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, fn: PopulatedEventArgs -> 'msg) = + this.AddScalar(AutoCompleteBox.Populated.WithValue(fn)) + + /// Listens to the AutoCompleteBox DropDownOpened event. + /// Current widget. + /// The IsOpen value. + /// Raised when the AutoCompleteBox DropDownOpened event is fired. + [] + static member inline onDropDownOpened(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, isOpen: bool, fn: bool -> 'msg) = + this.AddScalar(AutoCompleteBox.DropDownOpened.WithValue(ValueEventData.create isOpen fn)) + + /// Listens to the AutoCompleteBox SelectionChanged event. + /// Current widget. + /// Raised when the AutoCompleteBox SelectionChanged event is fired. + [] + static member inline onSelectionChanged(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, fn: SelectionChangedEventArgs -> 'msg) = + this.AddScalar(AutoCompleteBox.SelectionChanged.WithValue(fn)) + + /// Link a ViewRef to access the direct AutoCompleteBox control instance. + /// Current widget. + /// The ViewRef instance that will receive access to the underlying control. + [] + static member inline reference(this: WidgetBuilder<'msg, IFabAutoCompleteBox>, value: ViewRef) = + this.AddScalar(ViewRefAttributes.ViewRef.WithValue(value.Unbox)) + + /// Allows multi-binding the ValueMemberBinding on an AutoCompleteBox. + /// Current widget. + /// The format string to use. + /// The property names to bind. + [] + static member inline multiBindValue(this: WidgetBuilder<'msg, #IFabAutoCompleteBox>, format: string, [] propertyNames: string array) = + this.AddScalar(AutoCompleteBox.MultiValueBinding.WithValue((format, propertyNames))) diff --git a/src/Fabulous.Avalonia/ComponentViews/Controls/Border.fs b/src/Fabulous.Avalonia/ComponentViews/Controls/Border.fs new file mode 100644 index 00000000..745c1cbe --- /dev/null +++ b/src/Fabulous.Avalonia/ComponentViews/Controls/Border.fs @@ -0,0 +1,26 @@ +namespace Fabulous.Avalonia.Components + +open System.Runtime.CompilerServices +open Avalonia +open Avalonia.Controls +open Avalonia.Media +open Avalonia.Media.Immutable +open Fabulous +open Fabulous.Avalonia +open Fabulous.StackAllocatedCollections.StackList + +type IFabComponentBorder = + inherit IFabComponentDecorator + inherit IFabBorder + +[] +module ComponentBorderBuilders = + type Fabulous.Avalonia.Components.View with + + /// Creates a Border widget. + /// The content of the Border. + static member Border(content: WidgetBuilder) = + WidgetBuilder( + Border.WidgetKey, + AttributesBundle(StackList.empty(), ValueSome [| Decorator.ChildWidget.WithValue(content.Compile()) |], ValueNone) + ) diff --git a/src/Fabulous.Avalonia/ComponentViews/Controls/Buttons/Button.fs b/src/Fabulous.Avalonia/ComponentViews/Controls/Buttons/Button.fs new file mode 100644 index 00000000..ffadbf3f --- /dev/null +++ b/src/Fabulous.Avalonia/ComponentViews/Controls/Buttons/Button.fs @@ -0,0 +1,95 @@ +namespace Fabulous.Avalonia.Components + +open System.Runtime.CompilerServices +open Avalonia.Controls +open Avalonia.Input +open Fabulous +open Fabulous.StackAllocatedCollections.StackList + +type IFabButton = + inherit IFabContentControl + +module Button = + let WidgetKey = Widgets.register