Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gesture Modifiers #153

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 44 additions & 7 deletions samples/Gallery/Pages/GesturesPage.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
namespace Gallery.Pages

open Avalonia
open Avalonia.Controls
open Avalonia.Input
open Avalonia.Layout
open Avalonia.Media
open Fabulous
Expand All @@ -9,11 +11,16 @@ open Fabulous.Avalonia
open type Fabulous.Avalonia.View
open Gallery

// https://github.com/AvaloniaUI/Avalonia/blob/master/samples/ControlCatalog/Pages/GesturePage.cs
module GesturesPage =
type Model = { CurrentScale: float }

type Msg = | Reset
type Msg =
| Reset
| OnPullGesture of PullGestureEventArgs
| OnPinchGesture of PinchEventArgs
| OnScrollGesture of ScrollGestureEventArgs
| OnAttachedToVisualTree of VisualTreeAttachmentEventArgs
| OnTapGesture of TappedEventArgs

type CmdMsg = | NoMsg

Expand All @@ -28,10 +35,15 @@ module GesturesPage =
let update msg model =
match msg with
| Reset -> model, []
| OnPullGesture args -> model, []
| OnScrollGesture args -> model, []
| OnAttachedToVisualTree args -> model, []
| OnTapGesture tappedEventArgs -> model, []
| OnPinchGesture pinchEventArgs -> model, []

let view _ =
VStack(spacing = 4.) {
TextBlock("Pull Gexture (Touch / Pen)")
(VStack(spacing = 4.) {
TextBlock("Pull Gesture (Touch / Pen)")
.fontSize(18.)
.margin(5.)

Expand All @@ -58,6 +70,11 @@ module GesturesPage =
.horizontalAlignment(HorizontalAlignment.Stretch)
.height(50.)
.borderThickness(1.)
.gestureRecognizers() {
PullGestureRecognizer(OnPullGesture)
.pullDirection(PullDirection.TopToBottom)
}


Border(
Border()
Expand All @@ -77,6 +94,10 @@ module GesturesPage =
.horizontalAlignment(HorizontalAlignment.Stretch)
.height(50.)
.borderThickness(1.)
.gestureRecognizers() {
PullGestureRecognizer(OnPullGesture)
.pullDirection(PullDirection.BottomToTop)
}

Border(
Border()
Expand All @@ -97,6 +118,10 @@ module GesturesPage =
.verticalAlignment(VerticalAlignment.Stretch)
.width(50.)
.borderThickness(1.)
.gestureRecognizers() {
PullGestureRecognizer(OnPullGesture)
.pullDirection(PullDirection.RightToLeft)
}

Border(
Border()
Expand All @@ -117,6 +142,10 @@ module GesturesPage =
.verticalAlignment(VerticalAlignment.Stretch)
.width(50.)
.borderThickness(1.)
.gestureRecognizers() {
PullGestureRecognizer(OnPullGesture)
.pullDirection(PullDirection.LeftToRight)
}
})
.horizontalAlignment(HorizontalAlignment.Stretch)
.clipToBounds(true)
Expand All @@ -130,12 +159,20 @@ module GesturesPage =
.margin(5.)

Border(
Image(ImageSource.fromString "avares://Gallery/Assets/Icons/fabulous-icon.png", Stretch.UniformToFill)
.size(100., 100.)
Image(ImageSource.fromString "avares://Gallery/Assets/Icons/delicate-arch-896885_640.jpg", Stretch.UniformToFill)
.gestureRecognizers() {
PinchGestureRecognizer(OnPinchGesture)

ScrollGestureRecognizer(OnScrollGesture)
.canHorizontallyScroll(true)
.canVerticallyScroll(true)
}

)
.clipToBounds(true)

Button("Reset", Reset)
.horizontalAlignment(HorizontalAlignment.Center)
.name("ResetButton")
}
})
.onAttachedToVisualTree(OnAttachedToVisualTree)
69 changes: 69 additions & 0 deletions src/Fabulous.Avalonia/Attributes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ namespace Fabulous.Avalonia

open System
open System.Collections
open System.Collections.Generic
open System.ComponentModel
open Avalonia
open Avalonia.Collections
open Avalonia.Input.GestureRecognizers
open Avalonia.Interactivity
open Fabulous
open Fabulous.ScalarAttributeDefinitions
Expand Down Expand Up @@ -238,6 +240,73 @@ module Attributes =

Attributes.defineWidgetCollection name applyDiff updateNode

let defineAvaloniaGestureRecognizerCollection<'itemType> name (getCollection: obj -> GestureRecognizerCollection) =
let applyDiff _ (diffs: WidgetCollectionItemChanges) (node: IViewNode) =
let targetColl = getCollection node.Target
let targetColl = List<GestureRecognizer>(targetColl)

for diff in diffs do
match diff with
| WidgetCollectionItemChange.Remove(index, widget) ->
let itemNode = node.TreeContext.GetViewNode(box targetColl[index])

// Trigger the unmounted event
Dispatcher.dispatchEventForAllChildren itemNode widget Lifecycle.Unmounted
itemNode.Disconnect()

// Remove the child from the UI tree
targetColl.RemoveAt(index)

| _ -> ()

for diff in diffs do
match diff with
| WidgetCollectionItemChange.Insert(index, widget) ->
let struct (itemNode, view) = Helpers.createViewForWidget node widget

// Insert the new child into the UI tree
targetColl.Insert(index, unbox view)

// Trigger the mounted event
Dispatcher.dispatchEventForAllChildren itemNode widget Lifecycle.Mounted

| WidgetCollectionItemChange.Update(index, widgetDiff) ->
let childNode = node.TreeContext.GetViewNode(box targetColl[index])

childNode.ApplyDiff(&widgetDiff)

| WidgetCollectionItemChange.Replace(index, oldWidget, newWidget) ->
let prevItemNode = node.TreeContext.GetViewNode(box targetColl[index])

let struct (nextItemNode, view) = Helpers.createViewForWidget node newWidget

// Trigger the unmounted event for the old child
Dispatcher.dispatchEventForAllChildren prevItemNode oldWidget Lifecycle.Unmounted
prevItemNode.Disconnect()

// Replace the existing child in the UI tree at the index with the new one
targetColl[index] <- unbox view

// Trigger the mounted event for the new child
Dispatcher.dispatchEventForAllChildren nextItemNode newWidget Lifecycle.Mounted

| _ -> ()

let updateNode _ (newValueOpt: ArraySlice<Widget> voption) (node: IViewNode) =
let targetColl = getCollection node.Target
// let targetColl = List<GestureRecognizer>(targetColl)
// targetColl.Clear()

match newValueOpt with
| ValueNone -> ()
| ValueSome widgets ->
for widget in ArraySlice.toSpan widgets do
let struct (_, view) = Helpers.createViewForWidget node widget

targetColl.Add(unbox view)

Attributes.defineWidgetCollection name applyDiff updateNode

/// Define an attribute storing a Widget for an AvaloniaProperty
let inline defineAvaloniaPropertyWidget (property: AvaloniaProperty<'T>) =
Attributes.definePropertyWidget property.Name (fun target -> (target :?> AvaloniaObject).GetValue(property)) (fun target value ->
Expand Down
4 changes: 4 additions & 0 deletions src/Fabulous.Avalonia/Fabulous.Avalonia.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<Compile Include="Views\_Visual.fs" />
<Compile Include="Views\_Layoutable.fs" />
<Compile Include="Views\_Interactive.fs" />
<Compile Include="Views\_GestureRecognizer.fs" />
<Compile Include="Views\_InputElement.fs" />
<Compile Include="Views\_DragDrop.fs" />
<Compile Include="Views\_FlyoutBase.fs" />
Expand Down Expand Up @@ -213,6 +214,9 @@
<Compile Include="Views\Panels\DockPanel.fs" />
<Compile Include="Views\Panels\WrapPanel.fs" />
<Compile Include="Views\Panels\UniformGrid.fs" />
<Compile Include="Views\GestureRecognizers\PullGestureRecognizer.fs" />
<Compile Include="Views\GestureRecognizers\PinchGestureRecognizer.fs" />
<Compile Include="Views\GestureRecognizers\ScrollGestureRecognizer.fs" />
<Compile Include="Application.fs" />
<Compile Include="ThemeAware.fs" />
<Compile Include="Any.fs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Fabulous.Avalonia

open System.Runtime.CompilerServices
open Avalonia.Input
open Avalonia.Input.GestureRecognizers
open Fabulous
open Fabulous.StackAllocatedCollections
open Fabulous.StackAllocatedCollections.StackList

type IFabPinchGestureRecognizer =
inherit IFabGestureRecognizer

module PinchGestureRecognizer =
let WidgetKey = Widgets.register<PinchGestureRecognizer>()

[<AutoOpen>]
module PinchGestureRecognizerBuilders =
type Fabulous.Avalonia.View with

static member inline PinchGestureRecognizer<'msg>(gesture: PinchEventArgs -> 'msg) =
WidgetBuilder<'msg, IFabPinchGestureRecognizer>(
PinchGestureRecognizer.WidgetKey,
GestureRecognizer.Pinch.WithValue(fun args -> gesture args |> box)
)

[<Extension>]
type PinchGestureRecognizerModifiers =
[<Extension>]
static member inline onPinchEnded(this: WidgetBuilder<'msg, #IFabPinchGestureRecognizer>, onPinchEnded: PinchEndedEventArgs -> 'msg) =
this.AddScalar(GestureRecognizer.PinchEnded.WithValue(fun args -> onPinchEnded args |> box))
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace Fabulous.Avalonia

open System.Runtime.CompilerServices
open Avalonia.Input
open Fabulous

type IFabPullGestureRecognizer =
inherit IFabGestureRecognizer

module PullGestureRecognizer =
let WidgetKey = Widgets.register<PullGestureRecognizer>()

let PullDirection =
Attributes.defineAvaloniaPropertyWithEquality PullGestureRecognizer.PullDirectionProperty

[<AutoOpen>]
module PullGestureRecognizerBuilders =
type Fabulous.Avalonia.View with

static member inline PullGestureRecognizer<'msg>(gesture: PullGestureEventArgs -> 'msg) =
WidgetBuilder<'msg, IFabPullGestureRecognizer>(
PullGestureRecognizer.WidgetKey,
GestureRecognizer.PullGesture.WithValue(fun args -> gesture args |> box)
)

[<Extension>]
type PullGestureRecognizerModifiers =
[<Extension>]
static member inline pullDirection(this: WidgetBuilder<'msg, IFabPullGestureRecognizer>, value: PullDirection) =
this.AddScalar(PullGestureRecognizer.PullDirection.WithValue(value))

[<Extension>]
static member inline onPullGestureEnded(this: WidgetBuilder<'msg, #IFabPullGestureRecognizer>, onPullGestureEnded: PullGestureEndedEventArgs -> 'msg) =
this.AddScalar(GestureRecognizer.PullGestureEnded.WithValue(fun args -> onPullGestureEnded args |> box))
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
namespace Fabulous.Avalonia

open System.Runtime.CompilerServices
open Avalonia.Input
open Avalonia.Input.GestureRecognizers
open Fabulous

type IFabScrollGestureRecognizer =
inherit IFabGestureRecognizer

module ScrollGestureRecognizer =
let WidgetKey = Widgets.register<ScrollGestureRecognizer>()

let CanHorizontallyScroll =
Attributes.defineAvaloniaPropertyWithEquality ScrollGestureRecognizer.CanHorizontallyScrollProperty

let CanVerticallyScroll =
Attributes.defineAvaloniaPropertyWithEquality ScrollGestureRecognizer.CanVerticallyScrollProperty

let IsScrollInertiaEnabled =
Attributes.defineAvaloniaPropertyWithEquality ScrollGestureRecognizer.IsScrollInertiaEnabledProperty

let ScrollStartDistance =
Attributes.defineAvaloniaPropertyWithEquality ScrollGestureRecognizer.ScrollStartDistanceProperty

[<AutoOpen>]
module ScrollGestureRecognizerBuilders =
type Fabulous.Avalonia.View with

static member inline ScrollGestureRecognizer<'msg>(gesture: ScrollGestureEventArgs -> 'msg) =
WidgetBuilder<'msg, IFabScrollGestureRecognizer>(
ScrollGestureRecognizer.WidgetKey,
GestureRecognizer.ScrollGesture.WithValue(fun args -> gesture args |> box)
)

[<Extension>]
type ScrollGestureRecognizerModifiers =
[<Extension>]
static member inline canHorizontallyScroll(this: WidgetBuilder<'msg, IFabScrollGestureRecognizer>, value: bool) =
this.AddScalar(ScrollGestureRecognizer.CanHorizontallyScroll.WithValue(value))

[<Extension>]
static member inline canVerticallyScroll(this: WidgetBuilder<'msg, IFabScrollGestureRecognizer>, value: bool) =
this.AddScalar(ScrollGestureRecognizer.CanVerticallyScroll.WithValue(value))

[<Extension>]
static member inline isScrollInertiaEnabled(this: WidgetBuilder<'msg, IFabScrollGestureRecognizer>, value: bool) =
this.AddScalar(ScrollGestureRecognizer.IsScrollInertiaEnabled.WithValue(value))

[<Extension>]
static member inline scrollStartDistance(this: WidgetBuilder<'msg, IFabScrollGestureRecognizer>, value: int) =
this.AddScalar(ScrollGestureRecognizer.ScrollStartDistance.WithValue(value))

[<Extension>]
static member inline onScrollGestureInertiaStarting
(
this: WidgetBuilder<'msg, #IFabScrollGestureRecognizer>,
onScrollGestureInertiaStarting: ScrollGestureInertiaStartingEventArgs -> 'msg
) =
this.AddScalar(GestureRecognizer.ScrollGestureInertiaStarting.WithValue(fun args -> onScrollGestureInertiaStarting args |> box))

[<Extension>]
static member inline onScrollGestureEnded
(
this: WidgetBuilder<'msg, #IFabScrollGestureRecognizer>,
onScrollGestureEnded: ScrollGestureEndedEventArgs -> 'msg
) =
this.AddScalar(GestureRecognizer.ScrollGestureEnded.WithValue(fun args -> onScrollGestureEnded args |> box))
Loading
Loading