elmish / Elmish.WPF

Static WPF views for elmish programs
Other
434 stars 71 forks source link

Elmish.WPF vs Elmish.Uno #364

Open TysonMN opened 3 years ago

TysonMN commented 3 years ago

I just discovered that @xperiandri forked Elmish.WPF to create Elmish.Uno. That is great!

I don't know anything about Uno, but I have long thought that much of the code in Elmish.WPF should work for other Microsoft UI frameworks like UWP.

@xperiandri, if you don't mind, could you explain what Uno is and what code differences there are between Elmish.WPF and Elmish.Uno?

xperiandri commented 3 years ago

There are 2 main limitations of UWP (which is base of Uno):

  1. You don't create the first window explicitly, creating an application means creating a window.
  2. UWP does not support F# as the head project until it is integrated with WinUI in September-October 2021

There are 2 main differences to WPF:

  1. UWP's primary way of changing views is Frame that displays Pages. That means that some NavigationService is required so that you can switch pages within a Frame from Elmish program. It is available in the navigation branch, which also shows how to pass a parameter with submodelless approach.
  2. Because of the second limitation you can't call any platform logic in F#. Unless you use WinUI and .NET 5. But I haven't created a fork for that yet.

Elmish.Uno repository is a direct derivative of Elmish.WPF which has around a dozen of atomic commits that migrate Elmish.WPF to be usable with Uno. Also I added ModelTypeChanged property to ViewModel in order to determine model change when it is designed as discriminated union. Having model as discriminated union I map its name to XAML visual state name 1 to 1 and trigger change by name.

We have one Elmish.Uno pre-production project working on WASM .NET 3.1 and another preproduction project for iOS.

xperiandri commented 3 years ago

There are only a few commits related to the core:

  1. ModelTypeChanged event
  2. Derived ViewModel implementation in C# for UWP https://github.com/xperiandri/Elmish.Uno/blob/navigation/src/Elmish.Uno.Uwp/ViewModel.cs . As UWP requires ICustomPropertyProvider interface implementation for Bindings to work (interop from .NET to WinRT), DynamicObject does not work. This also forced me to create a Create method that creates ViewModel instead of calling a constructor.

The other commits port samples.

xperiandri commented 3 years ago

The other addition I want to be implemented after migration to .NET 5 is https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Xaml.Data.ISupportIncrementalLoading?view=winrt-19041

Where binding will allow to pass a command DU case that will be dispatched if incremental loading is requested

TysonMN commented 3 years ago

Interesting. Thanks for sharing all that information.

Eventually I will take a close look at your changes.

Feel free to suggest ideas that you think would make it easier for you to maintain your project.

xperiandri commented 3 years ago

I was managed to migrate to 3.5.7, but not to 4.0.0 (too many changes and absence of wrapDispatch)

TysonMN commented 3 years ago

Yes, we removed wrapDispatch because mapMsg is more important, and they are mutually exclusive. It makes Binding<'model, 'msg> a (covariant) functor in 'msg.

How do you want to use wrapDispatch?

TysonMN commented 3 years ago

And now I discovered that @xperiandri also forked unoplatform/Elmish.Uno, which also forked Elmish.WPF (in 2019), quickly added four commits, and has seen no changes since. Let's try to figure out a way keep the present Uno fork of Elmish.WPF going.

xperiandri commented 3 years ago

Yes, @jeromelaban did the first prototype on top of version 2. And I improved it and rebased it onto version 3.

TysonMN commented 3 years ago

Microsoft's Choose your Windows app platform documentation helped me better understand the difference between WPF and Uno. In short, UWP is sort of a successor of WPF. Then Uno is an extension of UWP that adds support for "iOS, Android, macOS, Linux and WebAssembly".

xperiandri commented 3 years ago

How do you want to use wrapDispatch?

This is what we do now

"Phone"      |> Binding.twoWay ((function
                                 | NoneUserInfo                         -> String.Empty
                                 | EmptyUserInfo    (phone, _)
                                 | NewUserInfo      (phone, _, _, _)
                                 | ExistingUserInfo (phone, _, _, _, _) -> phone),
                                InputPhone, throttle)
[<AutoOpen>]
module Dispatching =

    open System
    open Elmish
    open FSharp.Control.Reactive

    let asDispatchWrapper<'msg> (configure: IObservable<'msg> -> IObservable<'msg>)
                          (dispatch: Dispatch<'msg>) : Dispatch<'msg> =
        let subject = Subject.broadcast
        subject |> configure |> Observable.add dispatch
        fun msg -> async.Return (subject.OnNext msg) |> Async.Start

    let throttle<'msg> = Observable.throttle (TimeSpan.FromMilliseconds 500.) |> asDispatchWrapper<'msg>
TysonMN commented 3 years ago

Great. It is "just" throttling. I believe this can be added in a way that is compatible with mapMsg.

xperiandri commented 3 years ago

UWP is sort of a successor of WPF

WPF (full pack) -> Silverlight (added VisualStates, removed a lot of stuff) -> Silverlight on WIndows Phone (added additional thread (compositional) for smooth animations) -> WinRT (rewritten in C++ as COM on steroids with much better animation stack, similar to Silverlight) -> WinUI 3 (an attempt to reduce the gap to WPF)

xperiandri commented 2 years ago

@TysonMN will you have time this year to discuss and agree on a plan of aligning Elmish.Uno and Elmish.WPF cores so that I can simply do rebase on Elmish.WPF when you add new features and fixes? Agenda:

  1. Collection merge function (I need to record the difference in animation for you)
  2. INotifyDataErrorInfo implementation
  3. Extending Binding static class in order to support ISupportIncrementalLoading (probably we can simply add conditional compilation to Elmish.WPF). This is a simplest way for me to migrate to v4
  4. Elmish.Uno migration to v4 (If we fix point 2 and 3, I will just drop what I have now and use v4 code)
  5. Throttling
TysonMN commented 2 years ago

I can discuss here in GutHub almost every day. Did you have something else in mind?

Throttling shouldn't be an issue any more. The "throttling / warpDispatch API" from v3 had been added back to v4.

I am definitely sticking with the linear-time collection merge code (increase of the previous quadratic-time collection merge code).

Uno can certainly add to the binding API. Is that what you mean by item 3?

I think I understand item 2.

Re item 4, my guess is that starting over with v4 is the right move.