From 168867cea2227dac3610d4c152e6b6d3303f9f20 Mon Sep 17 00:00:00 2001 From: Angel Daniel Munoz Gonzalez Date: Sun, 19 Apr 2020 14:21:18 -0600 Subject: [PATCH 1/8] remove duplicated docs --- src/docs/_public/about.html | 267 ----------- src/docs/_public/blog.html | 216 --------- src/docs/_public/controls/Border.html | 299 ------------ src/docs/_public/controls/Button.html | 270 ----------- src/docs/_public/controls/CheckBox.html | 270 ----------- src/docs/_public/controls/DatePicker.html | 276 ----------- src/docs/_public/guides.html | 253 ---------- src/docs/_public/guides/Basic-Template.html | 371 --------------- src/docs/_public/guides/Full-Template.html | 358 -------------- src/docs/_public/guides/Home.html | 241 ---------- .../_public/guides/Quickstart-Template.html | 360 -------------- .../Unit-Testing-Avalonia-FuncUI-Apps.html | 438 ------------------ .../_public/guides/Views-and-Attributes.html | 337 -------------- src/docs/_public/index.html | 271 ----------- src/docs/_public/js/index.js | 8 - src/docs/_public/release-notes.html | 216 --------- src/docs/_public/style/style.css | 136 ------ 17 files changed, 4587 deletions(-) delete mode 100644 src/docs/_public/about.html delete mode 100644 src/docs/_public/blog.html delete mode 100644 src/docs/_public/controls/Border.html delete mode 100644 src/docs/_public/controls/Button.html delete mode 100644 src/docs/_public/controls/CheckBox.html delete mode 100644 src/docs/_public/controls/DatePicker.html delete mode 100644 src/docs/_public/guides.html delete mode 100644 src/docs/_public/guides/Basic-Template.html delete mode 100644 src/docs/_public/guides/Full-Template.html delete mode 100644 src/docs/_public/guides/Home.html delete mode 100644 src/docs/_public/guides/Quickstart-Template.html delete mode 100644 src/docs/_public/guides/Unit-Testing-Avalonia-FuncUI-Apps.html delete mode 100644 src/docs/_public/guides/Views-and-Attributes.html delete mode 100644 src/docs/_public/index.html delete mode 100644 src/docs/_public/js/index.js delete mode 100644 src/docs/_public/release-notes.html delete mode 100644 src/docs/_public/style/style.css diff --git a/src/docs/_public/about.html b/src/docs/_public/about.html deleted file mode 100644 index f689e2b..0000000 --- a/src/docs/_public/about.html +++ /dev/null @@ -1,267 +0,0 @@ - - - - - - Avalonia.FuncUI - - - - - - - - - - - -
- -
-
-
-

- About Avalonia.FuncUI -

-

- Don't hesitate to visit the following list of resources -

-
- - -
-
-
- - - - - - \ No newline at end of file diff --git a/src/docs/_public/blog.html b/src/docs/_public/blog.html deleted file mode 100644 index 45a39ed..0000000 --- a/src/docs/_public/blog.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - - Avalonia.FuncUI - - - - - - - - - - - -
- -
-
-
-

- Avalonia.FuncUI Blog -

-
-
-
-
- - - - - - \ No newline at end of file diff --git a/src/docs/_public/controls/Border.html b/src/docs/_public/controls/Border.html deleted file mode 100644 index 131238a..0000000 --- a/src/docs/_public/controls/Border.html +++ /dev/null @@ -1,299 +0,0 @@ - - - - - - Avalonia.FuncUI - - - - - - - - - - - -
- -
-
-
-

- Border -

-

- Controls -

-
-
-
-

Note: You can check the Avalonia docs for the Border if you need more information.

-

For Avalonia.FuncUI's DSL properties you can check Border.fs

-
-

The Border controll allows you to decorate child controls

-

Usage

-

Set Background -Avalonia.FuncUI has some overloads for you to take advantage on

-
Border.create [
-	Border.background "black"
-	Border.child [ StackPanel.create [ /* ... definition ... */ ] ]
-]
-Border.create [
-	Border.background "#000000"
-	Border.child [ StackPanel.create [ /* ... definition ... */ ] ]
-]
-
-
-

You can pass any IBrush compatible instance for the background for more control

-
-

Set Border Brush -Avalonia.FuncUI has some overloads for you to take advantage on

-
Border.create [
-	Border.borderBrush "red"
-	Border.child [ StackPanel.create [ /* ... definition ... */ ] ]
-]
-
-
Border.create [
-	Border.borderBrush "#FF0000"
-	Border.child [ StackPanel.create [ /* ... definition ... */ ] ]
-]
-
-
-

You can pass any IBrush compatible instance for the background for more control

-
-

Thickness

-
Border.create [
-	Border.borderThickness 2.0
-	Border.child [ StackPanel.create [ /* ... definition ... */ ] ]
-]
-
-

Horizontal and Vertical Thickness

-
Border.create [
-	Border.borderThickness 2.0 5.0
-	Border.child [ StackPanel.create [ /* ... definition ... */ ] ]
-]
-
-

Left, Top, Right, Bottom Thickness

-
Border.create [
-	Border.borderThickness 1.0 2.0 3.0 4.0
-	Border.child [ StackPanel.create [ /* ... definition ... */ ] ]
-]
-
-
-

You can also pass a Thickness struct to the borderThickness property

-
-

Corner Radius

-
Border.create [
-	Border.borderCorner Radius 2.0
-	Border.child [ StackPanel.create [ /* ... definition ... */ ] ]
-]
-
-

Horizontal and Vertical Corner Radius

-
Border.create [
-	Border.borderCorner Radius 2.0 5.0
-	Border.child [ StackPanel.create [ /* ... definition ... */ ] ]
-]
-
-

Left, Top, Right, Bottom Corner Radius

-
Border.create [
-	Border.borderCorner Radius 1.0 2.0 3.0 4.0
-	Border.child [ StackPanel.create [ /* ... definition ... */ ] ]
-]
-
-
-

You can also pass a Corner Radius struct to the cornerRadius property

-
- -
-
-
-
- - - - - - \ No newline at end of file diff --git a/src/docs/_public/controls/Button.html b/src/docs/_public/controls/Button.html deleted file mode 100644 index 9fdd858..0000000 --- a/src/docs/_public/controls/Button.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - Avalonia.FuncUI - - - - - - - - - - - -
- -
-
-
-

- Button -

-

- Controls -

-
-
-
-

Note: You can check the Avalonia docs for the Button if you need more information.

-

For Avalonia.FuncUI's DSL properties you can check Button.fs

-
-

Buttons are basic controls for any application you may build, buttons are often used to trigger an action.

-

Usage

-
-

You can check the general usage of Avalonia.FuncUI's views and attributes in the following link Views and Attributes

-
-

Create a Button

-
Button.create []
-
-

Register Click

-
Button.create [
-	Button.onClick(fun _ -> dispatch MyMsg)
-]
-
-

Set Click Mode

-
Button.create [
-	Button.clickMode ClickMode.Press
-]
-// or
-Button.create [
-	Button.clickMode ClickMode.Release
-]
-
-
-

for more information check the Click Mode docs

-
-

Set Content

-
Button.create [ Button.content "My Button" ]
-
-

Buttons can have arbitrary content, for example it can be a string as the example above. It also can be another entire control like a StackPanel

-
let playIcon = 
-	Canvas.create [ ... ]
-let textbox = 
-	TextBox.create [ ... ]
-
-let iconAndTextBlock = 
-	StackPanel.create [
-		StackPanel.orientation Horizontal
-		StackPanel.spacing 8.0
-		StackPanel.children [ playIcon; textbox ]
-	]
-Button.create [
-	Button.content iconAndTextBlock
-]
-
- -
-
-
-
- - - - - - \ No newline at end of file diff --git a/src/docs/_public/controls/CheckBox.html b/src/docs/_public/controls/CheckBox.html deleted file mode 100644 index 6024702..0000000 --- a/src/docs/_public/controls/CheckBox.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - Avalonia.FuncUI - - - - - - - - - - - -
- -
-
-
-

- CheckBox -

-

- Controls -

-
-
-
-

Note: You can check the Avalonia docs for the CheckBox and CheckBox API if you need more information.

-

For Avalonia.FuncUI's DSL properties you can check CheckBox.fs

-
-

The checkbox is a control that allows a user to represent boolean values or the absense of a value

-

Usage

-

Set Label

-
CheckBox.create [
-    Checkbox.content "I Accept the terms and conditions."
-]
-
-

Set Is Checked

-
CheckBox.create [
-    // can be either true or false
-    Checkbox.isChecked state.booleanValue
-]
-
-

Set Indeterminate

-
CheckBox.create [
-    // can be either true or false
-    CheckBox.isThreeState state.indeterminate
-    // this value is required to be either a nullable boolean
-    // or a boolean option
-    Checkbox.isChecked None
-]
-
-
-

To be able to set the indeterminate state, the isThreeState value must be true and the isChecked value must be None or Nullable boolean set to null

-
-

Set Dynamic State Checkbox

-

You can mix and match the three states of a checkbox. In this example -if the count value is greater than 0 the box will be checked, if the value is 0 then it will be indeterminate lastly if the value is less than 0 it will be unchecked

-
let isChecked = 
-    if state.count = 0 then
-        None
-    else if state.count > 0 then
-        Some true
-    else
-        Some false
-
-CheckBox.create [
-    CheckBox.content "Dynamic CheckBox"
-    // this is not required
-    Checkbox.isEnabled false 
-    CheckBox.isThreeState (state.count = 0)
-    CheckBox.isChecked isChecked
-]
-
- -
-
-
-
- - - - - - \ No newline at end of file diff --git a/src/docs/_public/controls/DatePicker.html b/src/docs/_public/controls/DatePicker.html deleted file mode 100644 index 9d985c3..0000000 --- a/src/docs/_public/controls/DatePicker.html +++ /dev/null @@ -1,276 +0,0 @@ - - - - - - Avalonia.FuncUI - - - - - - - - - - - -
- -
-
-
-

- DatePicker -

-

- Controls -

-
-
-
-

Note: You can check the Avalonia docs for the DatePicker API if you need more information.

-

For Avalonia.FuncUI's DSL properties you can check DatePicker.fs

-
-

The DatePicker control is a single date picker that displays a calendar, it is also posible to enter a date via the TextBox the control has

-

Usage

-

Set Date

-
DatePicker.create [
-    DatePicker.selectedDate DateTime.Today
-]
-
-

Set DateFormat

-
DatePicker.create [
-    DatePicker.selectedDateFormat DatePickerFormat.Long
-]
-
-DatePicker.create [
-    DatePicker.selectedDateFormat DatePickerFormat.Short
-]
-
-DatePicker.create [
-    DatePicker.selectedDateFormat DatePickerFormat.Custom
-    // It can be any valid DateFormat string
-    DatePicker.customDateFormatString "MMMM dd, yyyy"
-]
-
-
-

For more information about the DatePickerFormat check DatePickerFormat

-
-
-

You can check Custom date and time format strings Microsoft docs for more information about the format string

-
-

Set Start Display Date

-

Sets the first date available to display

-
let startFromYesterday =
-    DateTime.Today.Subtract(TimeSpan.FromDays(1.0))
-DatePicker.create [
-    DatePicker.displayDateStart startFromYesterday
-]
-
-

Set End Display Date

-

Sets the last date available to display

-
let showUpToTomorrow =
-    DateTime.Today.Add(TimeSpan.FromDays(1.0))
-DatePicker.create [
-    DatePicker.displayDateStart showUpToTomorrow
-]
-
-

Set Watermark

-

Sets the watermark (placeholder) for the TextBox that is included in this control

-
DatePicker.create [
-    DatePicker.watermark "Select a date"
-]
-
- -
-
-
-
- - - - - - \ No newline at end of file diff --git a/src/docs/_public/guides.html b/src/docs/_public/guides.html deleted file mode 100644 index b63ee5c..0000000 --- a/src/docs/_public/guides.html +++ /dev/null @@ -1,253 +0,0 @@ - - - - - - Avalonia.FuncUI - - - - - - - - - - - -
- -
- -
-
- - - - - - \ No newline at end of file diff --git a/src/docs/_public/guides/Basic-Template.html b/src/docs/_public/guides/Basic-Template.html deleted file mode 100644 index bc01fc8..0000000 --- a/src/docs/_public/guides/Basic-Template.html +++ /dev/null @@ -1,371 +0,0 @@ - - - - - - Avalonia.FuncUI - - - - - - - - - - - -
- -
-
-
-

- Basic Template -

-

- Avalonia Community -

-
-
-
-

The basic template contains three files

-
    -
  • {ProjectName}.fsproj
  • -
  • Counter.fs
  • -
  • Program.fs
  • -
-

this is the simplest way to get started with Avalonia.FuncUI it's a straight "counter" sample

-

Program.fs

-

Inside Program.fs two main classes allow you to start your Avalonia application

-

The first one you'll see it's

-
type MainWindow() as this =
-    inherit HostWindow()
-    do
-        base.Title <- "Basic"
-        base.Width <- 400.0
-        base.Height <- 400.0
-        
-        //this.VisualRoot.VisualRoot.Renderer.DrawFps <- true
-        //this.VisualRoot.VisualRoot.Renderer.DrawDirtyRects <- true
-
-
-        Elmish.Program.mkSimple (fun () -> Counter.init) Counter.update Counter.view
-        |> Program.withHost this
-        |> Program.run
-
-

this is (as the name says) the main window of the basic template, it inherits from HostWindow -which is a special class that adds some of the functionality needed to allow normal Avalonia Windows to work fine with the Elmish based approach.

-

Anything you would do with a window in Avalonia, you may do so with any class that inherits HostWindow, in this case, the most important part is the last three lines of code

-
Elmish.Program.mkSimple (fun () -> Counter.init) Counter.update Counter.view
-|> Program.withHost this
-|> Program.run
-
-

Here we're starting the Elmish program with our Counter module defined inside Counter.fs, then we set up the host Program.withHost and lastly run the program.

-

the second class is

-
type App() =
-    inherit Application()
-
-    override this.Initialize() =
-        this.Styles.Load "avares://Avalonia.Themes.Default/DefaultTheme.xaml"
-        this.Styles.Load "avares://Avalonia.Themes.Default/Accents/BaseDark.xaml"
-
-    override this.OnFrameworkInitializationCompleted() =
-        match this.ApplicationLifetime with
-        | :? IClassicDesktopStyleApplicationLifetime as desktopLifetime ->
-            desktopLifetime.MainWindow <- MainWindow()
-        | _ -> ()
-
-

This class is the equivalent of App.xaml and App.xaml.cs in Avalonia here we override the the Initialize() method to load the default styles that come from Avalonia, try switching BaseDark.xaml to BaseLight.xaml to use the light theme on your application, here you may also load your custom styles. For more information on that please check the Full Template.

-

the OnFrameworkInitializationCompleted() method is overridden to set our MainWindow class the application's MainWindow

-

Counter.fs

-

The counter is a plain Elmish module with the Avalonia.FuncUI DSL in action

-
namespace Basic
-
-module Counter =
-    open Avalonia.Controls
-    open Avalonia.FuncUI.DSL
-    open Avalonia.Layout
-
-    type State = { count : int }
-    type Msg = Increment | Decrement | Reset
-
-    let init = { count = 0 }
-    let update (msg: Msg) (state: State) : State =
-        match msg with
-        | Increment -> { state with count = state.count + 1 }
-        | Decrement -> { state with count = state.count - 1 }
-        | Reset -> init
-    
-    let view (state: State) (dispatch) =
-        DockPanel.create [
-            DockPanel.children [
-                Button.create [
-                    Button.dock Dock.Bottom
-                    Button.onClick (fun _ -> dispatch Reset)
-                    Button.content "reset"
-                ]                
-                Button.create [
-                    Button.dock Dock.Bottom
-                    Button.onClick (fun _ -> dispatch Decrement)
-                    Button.content "-"
-                ]
-                Button.create [
-                    Button.dock Dock.Bottom
-                    Button.onClick (fun _ -> dispatch Increment)
-                    Button.content "+"
-                ]
-                TextBlock.create [
-                    TextBlock.dock Dock.Top
-                    TextBlock.fontSize 48.0
-                    TextBlock.verticalAlignment VerticalAlignment.Center
-                    TextBlock.horizontalAlignment HorizontalAlignment.Center
-                    TextBlock.text (string state.count)
-                ]
-            ]
-        ]
-
-

You can refer the patern represented here as MVU (Model, View, Update) and can learn more about it here in the meantime, we will give a brief explanation here

-

State

-
type State = { count : int }
-
-

Also known as Model in the MVU acronym. The State type represents the shape of your data for this module.

-

In this case, we defined the shape of our data as { counter: int } which means our model has a counter that contains an integer value. The init function gives us the initial value which we used inside Program.fs

-
Elmish.Program.mkSimple (fun _ -> Counter.init) Counter.update Counter.view
-
-

in this case, the counter starts at 0 { count = 0 }

-

Msg

-

The Msg type allows us to identify which "events" is our application responding to

-
type Msg = Increment | Decrement | Reset
-
-

in this case, we have three possible events

-
    -
  • Increment
  • -
  • Decrement
  • -
  • Reset
  • -
-

these Msg types should be descriptive on what they are reacting/doing in your module

-

Update

-

In F# the data is often immutable this means every time you need to do changes you have to return updated copies of your data. The update function is the central place where you make changes to your state (model) depending on the message that was dispatched

-
let update (msg: Msg) (state: State) : State =
-    match msg with
-    | Increment -> { state with count = state.count + 1 }
-    | Decrement -> { state with count = state.count - 1 }
-    | Reset -> init
-
-

In this case, the messages are simple as well as the state, so the changes are pretty self-descriptive -Increment updates the count plus 1 integer, Decrement substracts 1 integer, Reset takes our initial model back to 0

-

View

-

This will be a brief explanation of the parts that glue everything together, to have other examples and a more concise explanation for the View Controls and their attributes, you can check Views and Attributes

-
let view (state: State) (dispatch: Msg -> unit) =
-    DockPanel.create [
-        DockPanel.children [
-            Button.create [
-                Button.onClick (fun _ -> dispatch Reset)
-            ]                
-            Button.create [
-                Button.onClick (fun _ -> dispatch Decrement)
-            ]
-            Button.create [
-                Button.onClick (fun _ -> dispatch Increment)
-            ]
-            TextBlock.create [
-                TextBlock.text (string state.count)
-            ]
-        ]
-    ]       
-
-

The view has two parameters: state which is the current state (model) of your module as well as a dispatch function, this dispatch function takes a Msg (It can be any of our Msg types: Increment, Decrement, Reset) and doesn't have a special return type (unit) Elmish and Avalonia.FuncUI glue the dispatch function. -to use the information that is stored in the state, we simply use state.{property} in this case, we have a text block (label) that renders out the current count on the screen.

- -
-
-
-
- - - - - - \ No newline at end of file diff --git a/src/docs/_public/guides/Full-Template.html b/src/docs/_public/guides/Full-Template.html deleted file mode 100644 index ae4c614..0000000 --- a/src/docs/_public/guides/Full-Template.html +++ /dev/null @@ -1,358 +0,0 @@ - - - - - - Avalonia.FuncUI - - - - - - - - - - - -
- -
-
-
-

- Full Template -

-

- Avalonia Community -

-
-
-
-
-

Note: It's recommended that you take a look at the Basic Template document before reading this document.

-
-

The Full Template contains 6 files

-
    -
  • {ProjectName}.fsproj
  • -
  • About.fs
  • -
  • Counter.fs
  • -
  • Shell.fs
  • -
  • Program.fs
  • -
  • Styles.xaml
  • -
-

You can check Counter.fs and Program.fs for more details.

-

About.fs

-

The "About" file is a pretty simple one, it contains links to useful resources, most of that functionality is explained in the Basic Template's Counter section. There's a piece though that can be useful if you intend to show links inside your application

-
let update (msg: Msg) (state: State) =
-    match msg with
-    | OpenUrl link -> 
-        let url = 
-            match link with 
-            | FuncUIGitter -> "https://gitter.im/Avalonia-FuncUI"
-                
-        if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then
-            let start = sprintf "/c start %s" url
-            Process.Start(ProcessStartInfo("cmd", start)) |> ignore
-        else if RuntimeInformation.IsOSPlatform(OSPlatform.Linux) then
-            Process.Start("xdg-open", url) |> ignore
-        else if RuntimeInformation.IsOSPlatform(OSPlatform.OSX) then
-            Process.Start("open", url) |> ignore
-        state, Cmd.none
-
-

Since Avalonia (therefore Avalonia.FuncUI) runs on top of .net core you can use .netstandard API's -here we use the following namespaces to determine on which platform we're running and what kind of way to open a link that works for our OS.

-
open System.Diagnostics
-open System.Runtime.InteropServices
-
-

Shell.fs

-

The Shell.fs file is a little more complex than the Counter or the Program you found in the Basic Template. Here we show you how you can use the TabControl to create an application that can show multiple views on the same module, people coming from a web background might think it looks like a Single Page Application and they would be right. All of the application is inside a single MainWindow class but, how does this impact the way the application is structured?

-

There are some ways to do it one of them is to use ViewBuilder.Create<MODULE_NAME.Host>([]) which we will talk about in the Quickstart Template -and the other is the approach used inside Shell.fs. The main points to talk about are

-
type State =
-    { aboutState: About.State; counterState: Counter.State;}
-type Msg =
-    | AboutMsg of About.Msg
-    | CounterMsg of Counter.Msg
-
-let init =
-    let aboutState, aboutCmd = About.init
-    let counterState = Counter.init
-    { aboutState = aboutState; counterState = counterState },
-    Cmd.batch [ aboutCmd ]
-
-let update (msg: Msg) (state: State): State * Cmd<_> =
-    match msg with
-    | AboutMsg bpmsg ->
-        let aboutState, cmd =
-            About.update bpmsg state.aboutState
-        { state with aboutState = aboutState },
-        /// map the message to the kind of message 
-        /// your child control needs to handle
-        Cmd.map AboutMsg cmd
-    /// ... omitted code
-
-let view (state: State) (dispatch) =
-   /// ... more omitted code
-   TabItem.content (Counter.view state.counterState (CounterMsg >> dispatch))
-   TabItem.content (About.view state.aboutState (AboutMsg >> dispatch)) 
-   /// ... mode omitted code
-
-

The main take away from this is that most Elmish modules are structured in the same way and contain the same public functions, therefore you can use Elmish modules inside other modules themselves (Shell.fs is a Elmish module that includes other two Elmish modules).

-

The state contains the children's state definitions

-
    -
  • aboutState: About.State;
  • -
  • counterState: Counter.State;
  • -
-

The Msg also contains the children's Msg types

-
    -
  • AboutMsg of About.Msg
  • -
  • CounterMsg of Counter.Msg
  • -
-

The init function also has some changes; There are modules that require some sort of initialization in those cases you often use Commands or Subscriptions and it's very likely that the init function of that module returns the state with a command, the module may not return a command at all just the initial state (model)

-
let init =
-    let aboutState, aboutCmd = About.init
-    let counterState = Counter.init
-    { aboutState = aboutState; counterState = counterState },
-    /// If your children controls don't emit any commands
-    /// in the init function, you can just return Cmd.none
-    /// otherwise, you can use a batch operation on all of them
-    /// you can add more init commands as you need
-    Cmd.batch [ aboutCmd ]
-
-

then we only need to assign the states where they belong and if we have a command from a child module, you can batch the commands (in case you have more than one) as shown in the sample code

-

In the update function we also make a similar change

-
let aboutState, cmd =
-    About.update bpmsg state.aboutState
-{ state with aboutState = aboutState },
-/// map the message to the kind of message 
-/// your child control needs to handle
-Cmd.map AboutMsg cmd
-
-

Here we're calling the child's module update function with the child's command, we simply take the returned state and command and apply them to our current state, and map the message to the kind of message it is Cmd.map AboutMsg cmd since the child module might be chaining messages (for example calling a Save and then returning the saved state and an AfterSave message

-

For the view function we use some composition (>>) to indicate what is the correct kind of dispatch that the child needs

-
let view (state: State) (dispatch) =
-   /// ... more omitted code
-   TabItem.content (Counter.view state.counterState (CounterMsg >> dispatch))
-   TabItem.content (About.view state.aboutState (AboutMsg >> dispatch)) 
-   /// ... mode omitted code
-
-

Here we use the view function from the Counter and the About modules.

-

Finally the MainWindow. You may ask yourself

-
-

Why would I need to have the window defined in the same module as my view?

-
-

The reason is that you are able to show an Elmish module inside its own window (yes you can have multiple windows) this only shows you that you don't necessarily need to define the MainWindow inside the Program.fs file. There's also another significant change

-
Elmish.Program.mkProgram (fun () -> init) update view
-
-

instead using mkSimple we now use mkProgram which takes a function that returns a State, Cmd -to begin the program.

-

Program.fs

-

The only difference this time is to include a custom styles file

-
this.Styles.Load "avares://PROJECT_NAME/Styles.xaml"
-
-

Styles

-

The Full Template includes a sample of custom styles, it is a simple Xaml file

-
<Styles
-    xmlns="https://github.com/avaloniaui"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
-    <Style Selector="Button /template/ ContentPresenter">
-        <Setter Property="CornerRadius" Value="5" />
-    </Style>
-<!-- ... more code -->
-</Styles>
-
-

You can read more about styles in the Avalonia Style's Docs -you are able to include other resources from other libraries if they expose them. One of our samples does this, you can check it here

- -
-
-
-
- - - - - - \ No newline at end of file diff --git a/src/docs/_public/guides/Home.html b/src/docs/_public/guides/Home.html deleted file mode 100644 index 4d9765f..0000000 --- a/src/docs/_public/guides/Home.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - Avalonia.FuncUI - - - - - - - - - - - -
- -
-
-
-

- Getting Started -

-

- Avalonia Community -

-
-
-
-

Welcome to the Avalonia.FuncUI wiki!

-

Setup

-

Setting up Avalonia.FuncUI is really simple, you can create a new project with a couple of commands:

-
dotnet new --install JaggerJo.Avalonia.FuncUI.Templates
-
-

and then choose one of the following

-
dotnet new funcui.basic -n NewApp
-
-
dotnet new funcui.full -n NewApp
-
-
dotnet new funcui.quickstart -n NewApp
-
-

You can check these templates in detail here

- - -
-
-
-
- - - - - - \ No newline at end of file diff --git a/src/docs/_public/guides/Quickstart-Template.html b/src/docs/_public/guides/Quickstart-Template.html deleted file mode 100644 index cb7df9d..0000000 --- a/src/docs/_public/guides/Quickstart-Template.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - Avalonia.FuncUI - - - - - - - - - - - -
- -
-
-
-

- Quickstart Template -

-

- Avalonia Community -

-
-
-
-

The Quickstart Template is intended to be used as a starting point for more serious projects, which means it includes more files that give you a lead on how you can do more complex things. It also is not intended to block you or dictate how you MUST do Avalonia.FuncUI applications. You can add/remove files/solutions as you want or need. It should not impede you, as we explained in the Basic Template all you need is a couple of files and .net core installed to get up and running.

-

File Structure

-

This is the File Structure for the Quickstart Template

-
│   ProjectName.sln
-│
-├───ProjectName
-│       About.fs
-│       Program.fs
-│       ProjectName.fsproj
-│       Shell.fs
-│       Styles.xaml
-│       TreeViewPage.fs
-│       UserProfiles.fs
-│
-├───ProjectName.Core
-│       Library.fs
-│       ProjectName.Core.fsproj
-│       Users.fs
-│
-└───ProjectName.Core.Tests
-        Main.fs
-        Sample.fs
-        ProjectName.Core.Tests.fsproj
-
-

We offer three main projects

-
    -
  • ProjectName
  • -
  • ProjectName.Core
  • -
  • ProjectName.Core.Tests
  • -
-

ProjectName is where your Avalonia.FuncUI code resides, it is your application.

-

ProjectName.Core is where you may have Shared logic that can be reused between different kinds of solutions like Web or Console in case you decide to add these solutions for your project (having a Shared/Core is not required).

-

ProjectName.Core.Tests is a Test Project based on Expecto an F# Testing library. It is not required to use Expecto, you can replace it with any testing library you see fit for your needs.

-

ProjectName

-

The ProjectName directory contains seven files, some of those have been previously discussed in the Basic Template and Full Template doc pages. There are a couple of differences on the Shell.fs file which will be covered in short. There are also two new pages

-
    -
  • TreeViewPage.fs
  • -
  • UserProfiles.fs
  • -
-

TreeViewPage.fs

-

TreeViewPage is a simple Elmish module that shows you how to use an Avalonia Tree Control

-
type Taxonomy =
-  { Name: string
-    Children: Taxonomy seq }
-
-

That is the main Tree structure that is going to be represented inside this Elmish module

-
/// ... omitted code
-TreeView.create
-    [ TreeView.dock Dock.Left
-      /// dataItems refers to the source of your control's data
-      /// these are going to be iterated to fill your template's contents
-      TreeView.dataItems [ food ]
-      TreeView.itemTemplate
-          /// You can pass the type of your data collection
-          /// to have a safe type reference in the create function
-          (DataTemplateView<Taxonomy>
-              .create
-                  ((fun data -> data.Children),
-                    (fun data ->
-                        TextBlock.create
-                            [ TextBlock.onTapped (fun _ -> dispatch (ShowDetail data))
-                              TextBlock.text data.Name ]))) ]
-/// ... more omitted code
-
-

Just as you would do in a XAML based approach, you can use DataTemplates in Avalonia.FuncUI. -.dataItems represents the data you want to use with this control it can be any kind of data, the item template uses DataTemplateView<'t> with a couple of functions to declare how the control should look like -as always you can use any control you deem necessary in your data template.

-

From there on our Elmish module is almost equal as any other module we've seen so far. There's a key difference though and that is that this module exposes a Host Type

-
type Host() as this =
-    inherit Hosts.HostControl()
-    do
-        /// You can use `.mkProgram` to pass Commands around
-        /// if you decide to use it, you have to also return a Command in the initFn
-        /// (init, Cmd.none)
-        /// you can learn more at https://elmish.github.io/elmish/basics.html
-        let startFn () =
-            init
-        Elmish.Program.mkSimple startFn update view
-        |> Program.withHost this
-        |> Program.run
-
-

HostControls are standalone controls, this is a different approach from what we saw at Shell.fs when including children structures, in that module we used the functions exposed by the About.fs and Counter.fs and integrated them with Shell our workflow, so we could intercept messages and act accordingly on the Shell module (for a better example on that check the Music Player's Update function). The Host control can act as an individual Control itself and handle its workflow without having to expose its functions to the Shell module. If you know your control doesn't need any external input and doesn't have any output that might affect other Controls in your application (the about page could be an example of that as well) perhaps you may want to use the HostControl approach.

-

UserProfiles.fs

-

The UserProfiles page exposes a HostControl similarly to the TreeViewPage. It also shows you some ways on how to work with internet resources like a Restful API

-

What is different from other modules?

-
    -
  • The usage of Cmd.ofAsync
  • -
-

our init function is defined as

-
/// sample function to load the initial data
-let loadInit() =
-    /// this comes from our `ProjectName.Core` solution
-    Users.getUsers None None
-
-type State =
-    { users: (Users.UserEndpoint.Result * Bitmap) array }
-
-type Msg =
-    | SetUsers of (Users.UserEndpoint.Result * Bitmap) array
-    | LoadImages of Users.UserEndpoint.Result array
-
-let init = { users = Array.empty }, Cmd.OfAsync.perform loadInit () LoadImages
-
-

Elmish provides a module OfAsync that has great functions to allows us to have a seamless workflow with async interactions as well as a module OfTask in case we need to deal with System.Threading.Tasks.Task.

-

When the init function is called by Elmish.Program.mkProgram... we schedule an async command that upon successful execution it will invoke the LoadImages of Users.UserEndpointResult array message, that way we're loading the initial payload for this Control fetching the resources over the network.

-

The update function also shows a little bit of how neat is to work with F#'s Async

-
let update (msg: Msg) (state: State): State * Cmd<_> =
-    match msg with
-    | LoadImages users ->
-        let loadingImgs() =
-            async {
-                let! requests = users
-                                |> Array.map (fun user -> Users.getImageFromUrl user user.Picture.Large)
-                                |> Async.Parallel
-                return requests |> Array.Parallel.map (fun (user, src) -> user, new Bitmap(src))
-            }
-        state, Cmd.OfAsync.perform loadingImgs () SetUsers
-    | SetUsers users ->
-        { state with users = users }, Cmd.none
-
-

loadingImgs() is an async function that does a bit of Parallel processing, if you come from javascript'land this is a version of Promise.all(promiseArray). Once we're done with our async request we simply return what's expected from the update function, The State and the Command in this case an async command and the unmodified state, to update the state upon completion we'll use SetUsers of (Users.UserEndpoint.Result * Bitmap) array. The rest should be familiar and consistent with other the other templates we've discussed before

-

ProjectName.Core

-

The core solution is a simple .netstandard2.0 F# library. If you take a look at the Library.fs file you'll see the default values from dotnet new classlib -lang F#

-
module Say =
-    let hello name = sprintf "Hello, %s" name
-
-

Users.fs

-

the Users.fs file includes an example of fetching resources over the network using F#'s Type Providers which is a Type Safe way to interact with a Public Restful API. Here we used the Random User API

-

ProjectName.Core.Tests

-

Lastly, the Tests solution includes a sample suite of tests including a Hello World test

-
test "Hello, World!" {
-    /// Testing our Core Library
-    let actual = hello "World!"
-    Expect.equal actual "Hello, World!" "hello Should say Hello, World!"
-}
-
-

If you wonder how to test your application using Expecto you can find that here

- -
-
-
-
- - - - - - \ No newline at end of file diff --git a/src/docs/_public/guides/Unit-Testing-Avalonia-FuncUI-Apps.html b/src/docs/_public/guides/Unit-Testing-Avalonia-FuncUI-Apps.html deleted file mode 100644 index e087389..0000000 --- a/src/docs/_public/guides/Unit-Testing-Avalonia-FuncUI-Apps.html +++ /dev/null @@ -1,438 +0,0 @@ - - - - - - Avalonia.FuncUI - - - - - - - - - - - -
- -
-
-
-

- Unit Testing Avalonia.FuncUI -

-

- Avalonia Community -

-
-
-
-

Testing Avalonia.FuncUI apps is pretty simple if you have previous experience using Elmish it should be not much different -for the moment let's dive into it.

-
-

Note: For this document, we'll use Expecto unit test library.

-
-

I'm using Powershell Core but feel free to follow on the terminal you like most (if it's bash like remember to change ; for &)

-
PS ~/github> mkdir TestingExample; cd TestingExample
-
-PS ~/github/TestingExample> dotnet new sln
-The template "Solution File" was created successfully.
-
-PS ~/github/TestingExample> dotnet new funcui.full -o TestingExample ; dotnet new expecto -o TestingExample.Tests
-The template "Avalonia FuncUI App (with extras)" was created successfully.
-The template "Expecto .net core Template" was created successfully.
-
-PS ~/github/TestingExample> dotnet sln add TestingExample ; dotnet sln add TestingExample.Tests
-Project `TestingExample\TestingExample.fsproj` added to the solution.
-Project `TestingExample.Tests\TestingExample.Tests.fsproj` added to the solution.
-
-PS ~/github/TestingExample> dotnet add TestingExample.Tests reference TestingExample
-Reference `..\TestingExample\TestingExample.fsproj` added to the project.
-
-
-

This gives us a Full Template and a Expecto project to start our testing.

-

First, let's replace the default content of the tests inside TestingExample.Tests.Sample.fs with the following

-
module Tests
-
-open Expecto
-
-[<Tests>]
-let tests =
-  testList "Counter Tests" []
-
-

Our Full Template has a fully working counter ready to run.

-
namespace TestingExample
-
-module Counter =
-    open Avalonia.Controls
-    open Avalonia.FuncUI.DSL
-    open Avalonia.Layout
-    
-    type State = { count : int }
-    let init = { count = 0 }
-
-    type Msg = Increment | Decrement | Reset
-
-    let update (msg: Msg) (state: State) : State =
-        match msg with
-        | Increment -> { state with count = state.count + 1 }
-        | Decrement -> { state with count = state.count - 1 }
-        | Reset -> init
-    
-    let view (state: State) (dispatch) =
-        DockPanel.create [
-            DockPanel.children [
-                StackPanel.create [
-                    StackPanel.dock Dock.Bottom
-                    StackPanel.margin 5.0
-                    StackPanel.spacing 5.0
-                    StackPanel.children [
-                        Button.create [
-                            Button.onClick (fun _ -> dispatch Increment)
-                            Button.content "+"
-                            Button.classes [ "plus" ]
-                        ]
-                        Button.create [
-                            Button.onClick (fun _ -> dispatch Decrement)
-                            Button.content "-"
-                            Button.classes [ "minus" ]
-                        ]
-                        Button.create [
-                            Button.onClick (fun _ -> dispatch Reset)
-                            Button.content "reset"
-                        ]                         
-                    ]
-                ]
-
-                TextBlock.create [
-                    TextBlock.dock Dock.Top
-                    TextBlock.fontSize 48.0
-                    TextBlock.verticalAlignment VerticalAlignment.Center
-                    TextBlock.horizontalAlignment HorizontalAlignment.Center
-                    TextBlock.text (string state.count)
-                ]
-            ]
-        ]
-
-

let's start adding test cases, Increment first:

-
module Tests
-
-open Expecto
-open TestingExample
-
-[<Tests>]
-let tests =
-    testList "Counter Tests"
-        [ testCase "Increment should increment counter by 1" <| fun _ ->
-            let initialState: Counter.State = { count = 1 }
-            let updateMessages = [ Counter.Msg.Increment; Counter.Msg.Increment ]
-
-            let actual =
-                updateMessages |> List.fold (fun state message -> Counter.update message state) initialState
-            Expect.equal actual.count 2 "Expected count to be 2" ]
-
-

If you run that code with dotnet test right now you will get a failure

-
Microsoft (R) Test Execution Command Line Tool Version 16.3.0
-Copyright (c) Microsoft Corporation.  All rights reserved.
-
-Starting test execution, please wait...
-
-A total of 1 test files matched the specified pattern.
-
-  X Counter Tests/Increment should increment counter by 1 [65ms]
-  Error Message:
-   
-Expected count to be 2.
-expected: 2
-  actual: 3
-  // ... stack trace ...
-
-Test Run Failed.
-Total tests: 1
-     Passed: 0
-     Failed: 1
-
-

the simple fix is to change this line

-
let initialState: Counter.State = { count = 1 }
-
-

to

-
let initialState: Counter.State = { count = 0 }
-
-

If you run dotnet test again

-
Microsoft (R) Test Execution Command Line Tool Version 16.3.0
-Copyright (c) Microsoft Corporation.  All rights reserved.
-
-Starting test execution, please wait...
-
-A total of 1 test files matched the specified pattern.
-
-
-Test Run Successful.
-Total tests: 1
-     Passed: 1
-
-

let's add the Decrement test

-
module Tests
-
-open Expecto
-open TestingExample
-
-[<Tests>]
-let tests =
-    testList "Counter Tests"
-        [ // ... testCase "Increment should increment counter by 1" ...
-          testCase "Decrement should decrement counter by 1" <| fun _ ->
-              let initialState: Counter.State = { count = 0 }
-              let updateMessages = [ Counter.Msg.Decrement; Counter.Msg.Decrement ]
-
-              let actual =
-                  updateMessages |> List.fold (fun state message -> Counter.update message state) initialState
-              Expect.equal actual.count -2 "Expected count to be -2" ]
-
-

Pretty simple huh?

-

Let's add the Reset test case

-
module Tests
-
-open Expecto
-open TestingExample
-
-[<Tests>]
-let tests =
-    testList "Counter Tests"
-        [ // ... testCase "Increment should increment counter by 1" ...
-          // ..testCase "Decrement should decrement counter by 1" ...
-          testCase "Reset should get counter back to 0" <| fun _ ->
-              let initialState: Counter.State = { count = 5 }
-
-              let actual = Counter.update Counter.Reset initialState
-              Expect.equal actual.count 0 "Expected count to be 0" ]
-
-

the advantage of having a central place to do updates and that we always return a State is that we can provide the exact state we want and have a predictable test ouput.

-

You don't need to provide a list of Msg's but here we put a simple example on how can you provide a set of "steps" to archieve a specific state, this could be useful to you if you need to test a specific workflow or something similar.

-

Lastly, Here's the full suite of tests.

-
module Tests
-
-open Expecto
-open TestingExample
-
-[<Tests>]
-let tests =
-    testList "Counter Tests"
-        [ testCase "Increment should increment counter by 1" <| fun _ ->
-            let initialState: Counter.State = { count = 0 }
-            // "fire" two increments
-            let updateMessages = [ Counter.Msg.Increment; Counter.Msg.Increment ]
-
-            let actual =
-                updateMessages |> List.fold (fun state message -> Counter.update message state) initialState
-            Expect.equal actual.count 2 "Expected count to be 2"
-          testCase "Decrement should decrement counter by 1" <| fun _ ->
-              let initialState: Counter.State = { count = 0 }
-              // "fire" two decrements
-              let updateMessages = [ Counter.Msg.Decrement; Counter.Msg.Decrement ]
-
-              let actual =
-                  updateMessages |> List.fold (fun state message -> Counter.update message state) initialState
-              Expect.equal actual.count -2 "Expected count to be -2"
-          testCase "Reset should get counter back to 0" <| fun _ ->
-              // set a initial state different from 0
-              let initialState: Counter.State = { count = 5 }
-
-              let actual = Counter.update Counter.Reset initialState
-              Expect.equal actual.count 0 "Expected count to be 0" ]
-
-
- -
-
-
-
- - - - - - \ No newline at end of file diff --git a/src/docs/_public/guides/Views-and-Attributes.html b/src/docs/_public/guides/Views-and-Attributes.html deleted file mode 100644 index 75a3f25..0000000 --- a/src/docs/_public/guides/Views-and-Attributes.html +++ /dev/null @@ -1,337 +0,0 @@ - - - - - - Avalonia.FuncUI - - - - - - - - - - - -
- -
-
-
-

- Views And Attributes -

-

- Avalonia Community -

-
-
-
-

F# is a great language to built DSLs (Domain Specific Languages), this library contains a elm like DSL used to describe Views.

-
let view (state: CounterState) (dispatch): View =
-    DockPanel.create [
-        DockPanel.children [
-            Button.create [
-                Button.dock Dock.Bottom
-                Button.onClick (fun _ -> dispatch Decrement)
-                Button.content "-"
-            ]
-            Button.create [
-                Button.dock Dock.Bottom
-                Button.onClick (fun _ -> dispatch Increment)
-                Button.content "+"
-            ]
-            TextBlock.create [
-                TextBlock.dock Dock.Top
-                TextBlock.fontSize 48.0
-                TextBlock.verticalAlignment VerticalAlignment.Center
-                TextBlock.horizontalAlignment HorizontalAlignment.Center
-                TextBlock.text (string state.count)
-            ]
-        ]
-    ]   
-
- -

🔰 Basics

-

Support for all Avalonia Controls is built-in. You can use them like this:

-
-

{ControlName}.create [ {ControlName}.{attributeName} ]

-
-
Button.create [ Button.content ""; Button.onClick(fun args -> printf "%A" args) ]
-TextBox.create [ Textbox.watermark "I'm a placeholder"; Textbox.text state.textboxValue ]
-Border.Create [ attributes ]
-TabControl.Create [ attributes ]
-...
-
-

Attributes are type-safe, so you don't have to remember what Attributes a Button has. The compiler will complain if you try to use an unsupported attribute 😉. (This applies to all kinds of attributes, including events, attached properties, ...)

-
Button.create [
-    Button.margin 5.0
-    Button.content "button text"
-    Button.children … // ⚠ compiler error - not supported on button
-]
-...
-
-

Also if you, for example, try to set the margin there are several overloads available to simplify what you are trying to do.

-
Button.margin 5.0
-TextBox.margin (5.0, 5.0)
-TextBlock.margin (horizontal = 5.0, vertical = 5.0)
-TabControl.margin (5.0, 5.0, 5.0, 5.0)
-ListBox.margin (left = 5.0, top = 5.0, right = 5.0, bottom = 5.0)
-
-

Pretty neat, Hah. Those overloads exist for a lot of attributes (you can also add them yourself), my favorite on is:

-
Button.background "green" // or "#00ff00"
-
-

🔧 Properties

-

For each .NET Property defined on an Avalonia Control there is a corresponding Attribute. Most of them are Property Attributes, but not all of them.

-
Button.create [
-    Button.margin 5.0
-    Button.content "button text"
-]
-...
-
-

⚡ Events

-

Events are just like other attributes. You can easily recognize them by their prefix. Events are named like this

-
-

{ControlName}.on**{EventName}**

-
-
TextBox.onClick (fun sender args -> // do something )
-TextBox.onKeyDown (fun sender args -> // do something )
-TextBox.onKeyUp (fun sender args -> // do something )
-TextBox.onSelectionChanged (fun sender args -> // do something )
-...
-
-

🧲 Attached Properties

-

Attached Attributes are used like Events and normal Properties.

-
-

{ControlName}.{name}

-
-
StackPanel.dock Dock.Top
-StackPanel.row 1
-StackPanel.colum 1
-...
-
-
-

⚠ Currently not all attached properties are supported / declared. This is currently in process, feel free to create an issue if something is missing

-
-

📦 Content Properties

-

Content Properties are attributes containing either a single View or a list of Views. They are often named content, children, viewItems, … you get it.

-

Here are some examples.

-
// single view content
-Button.create [
-    // takes 'View' 
-    Button.content (
-        TextBlock.create [
-            TextBlock.text "some text"
-        ]
-    )
-]
-
-// content view list
-StackPanel.create [
-    // takes 'View list' 
-    StackPanel.children [
-        TextBox.create [
-            TextBox.text "one"
-        ]
-        TextBox.create [
-            TextBox.text "two"
-        ]
-        ...
-    ]
-]   
-
-
- -
-
-
-
- - - - - - \ No newline at end of file diff --git a/src/docs/_public/index.html b/src/docs/_public/index.html deleted file mode 100644 index dd9f968..0000000 --- a/src/docs/_public/index.html +++ /dev/null @@ -1,271 +0,0 @@ - - - - - - Avalonia.FuncUI - - - - - - - - - - - -
- -
-
-

Avalonia FuncUI

-

Avalonia FuncUI

-

Develop cross-platform MVU GUI Applications using F# and Avalonia!

-

- -GitHub top language -GitHub repo size - - -

-
-

-(Application was created using Avalonia.FuncUI!)

-

About

-

This library allows you to write cross-platform GUI Applications entirely in F# - No XAML, but a declarative Elm-like DSL. MVU (Model-View-Update) architecture support is built in, and bindings to use it with Elmish are also ready to use.

-

Getting started

-

Check out the Wiki and Examples.

-

🧱 Templates

-

Contributing

-

Please contribute to this library through issue reports, pull requests, code reviews, documentation, and discussion.

-

Example

-

Below is the code of a simple counter app (using the Avalonia.FuncUI.Elmish package).

-
module Counter =
-
-    type CounterState = {
-        count : int
-    }
-
-    let init = {
-        count = 0
-    }
-
-    type Msg =
-    | Increment
-    | Decrement
-
-    let update (msg: Msg) (state: CounterState) : CounterState =
-        match msg with
-        | Increment -> { state with count =  state.count + 1 }
-        | Decrement -> { state with count =  state.count - 1 }
-    
-    let view (state: CounterState) (dispatch): IView =
-        DockPanel.create [
-            DockPanel.children [
-                Button.create [
-                    Button.onClick (fun _ -> dispatch Increment)
-                    Button.content "click to increment"
-                ]
-                Button.create [
-                    Button.onClick (fun _ -> dispatch Decrement)
-                    Button.content "click to decrement" 
-                ]
-                TextBlock.create [
-                    TextBlock.dock Dock.Top
-                    TextBlock.text (sprintf "the count is %i" state.count)
-                ]
-            ]
-        ]    
-
- -
-
-
- - - - - - \ No newline at end of file diff --git a/src/docs/_public/js/index.js b/src/docs/_public/js/index.js deleted file mode 100644 index 280cfe9..0000000 --- a/src/docs/_public/js/index.js +++ /dev/null @@ -1,8 +0,0 @@ -window.addEventListener('DOMContentLoaded', () => { - const navbarmenu = document.querySelector('[data-target=navbarMenu]'); - const menu = document.querySelector('#navbarMenu'); - navbarmenu.addEventListener('click', () => { - navbarmenu.classList.toggle('is-active'); - menu.classList.toggle('is-active'); - }); -}); \ No newline at end of file diff --git a/src/docs/_public/release-notes.html b/src/docs/_public/release-notes.html deleted file mode 100644 index 033942f..0000000 --- a/src/docs/_public/release-notes.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - - Avalonia.FuncUI - - - - - - - - - - - -
- -
-
-
-

- Release Notes -

-
-
-
-
- - - - - - \ No newline at end of file diff --git a/src/docs/_public/style/style.css b/src/docs/_public/style/style.css deleted file mode 100644 index d49d202..0000000 --- a/src/docs/_public/style/style.css +++ /dev/null @@ -1,136 +0,0 @@ -html, body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; - background: #F0F2F4; -} - -.main-content { - display: grid; - grid-template: 'menu content' minmax(100vh, 1fr) / 0.25fr 0.75fr; - gap: 1em; - margin: 1em; - grid-auto-flow: dense; -} - -.about { - display: grid; - grid-template: - 'aboutheader aboutheader' auto - 'left right' auto / 1fr 1fr; - gap: 1em; - margin: 1em; - grid-auto-flow: dense; -} - -@media screen and (max-width: 1023px) { - .main-content { - grid-template: 'content' minmax(0, 1fr) / minmax(0, 1fr); - } -} - -@media screen and (max-width: 1023px) { - .navbar.is-fixed-top .navbar-menu, .navbar.is-fixed-top-touch .navbar-menu { - background-color: #643580; - color: white; - } -} - -@media screen and (max-width: 768px) { - .about { - grid-template: - 'aboutheader' auto - 'left' auto - 'right' / auto; - } -} - -.about-header { - grid-area: aboutheader; -} - -.about .left { - grid-area: left; -} - -.about .right { - grid-area: right; -} - -.link-list-item { - font-size: 1.5em; -} - -.main-footer { - background-color: #e8e8e8; -} - -.navbar-burger { - background-color: #643580; - color: white; -} - -.page-content { - grid-area: content; -} - -.brand-link { - background-color: white; -} - -.navbar-item, -.navbar-link { - color: white; -} - -.is-funcui { - background-color: #643580; - color: white; -} - - .is-funcui - .navbar-item.is-active:not(:focus):not(:hover) { - background-color: #F0F2F4; - color: #A9A9A9; - } - -.post .post-header { - text-align: center -} - -.post-content { - padding-top: 1em; -} - -.blog .blog-header { - text-align: center; -} - -.release-note .release-note-header { - text-align: center -} - -.release-notes .release-notes-header { - text-align: center -} - -.control .control-header { - text-align: center -} - -.guide-content .number, -.post-content .number, -.release-note-content .number, -.index-content .number, -.control-content .number{ - align-items: unset; - background-color: unset; - border-radius: unset; - display: unset; - font-size: unset; - height: unset; - justify-content: unset; - margin-right: unset; - min-width: unset; - padding: unset; - text-align: unset; - vertical-align: unset; -} From ebb930440bd83fffa149fc3eae206090d0b09b8d Mon Sep 17 00:00:00 2001 From: Angel Daniel Munoz Gonzalez Date: Sun, 19 Apr 2020 14:25:29 -0600 Subject: [PATCH 2/8] ignore directories --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index d2e5a9d..c835854 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ src/docs/.fake +src/docs/_public/* *.DS_Store +.fake +.ionide +.vscode \ No newline at end of file From b33d6171afcb755b9f31112154601318d27ac976 Mon Sep 17 00:00:00 2001 From: Angel Daniel Munoz Gonzalez Date: Sun, 19 Apr 2020 14:25:56 -0600 Subject: [PATCH 3/8] keep these directories to prevent compilation errors untill they have some content --- src/docs/posts/.gitkeep | 0 src/docs/release-notes/.gitkeep | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/docs/posts/.gitkeep create mode 100644 src/docs/release-notes/.gitkeep diff --git a/src/docs/posts/.gitkeep b/src/docs/posts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/docs/release-notes/.gitkeep b/src/docs/release-notes/.gitkeep new file mode 100644 index 0000000..e69de29 From 75942e5afbbfc4412803a055f9920f820b109f80 Mon Sep 17 00:00:00 2001 From: Angel Daniel Munoz Gonzalez Date: Sun, 19 Apr 2020 14:26:09 -0600 Subject: [PATCH 4/8] add NativeMenu Docs --- src/docs/controls/NativeMenu.md | 89 +++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/docs/controls/NativeMenu.md diff --git a/src/docs/controls/NativeMenu.md b/src/docs/controls/NativeMenu.md new file mode 100644 index 0000000..ba2fb0b --- /dev/null +++ b/src/docs/controls/NativeMenu.md @@ -0,0 +1,89 @@ +--- +layout: control +name: NativeMenu +group: controls +--- +[NativeMenu]: https://avaloniaui.net/docs/controls/nativemenu +[announcement]: https://avaloniaui.net/blog/#osx-linux-native-menus +[in this issue]: https://github.com/AvaloniaCommunity/Avalonia.FuncUI/issues/113 + +> You can check the [NativeMenu] Avalonia docs for more information + +Native menus were introduced in Avalonia in version 0.9.0 you can check the [announcement] to see a brief explanation on how to use them in Avalonia Applications. + +Currently for Avalonia.FuncUI there is not a DSL and the NativeMenu control is in a weird spot for Avalonia.FuncUI since this control works directly on the main Application/Window object so it's though to pull a DSL on top of that. But! thankfully you can just use plain F# for the menu as noted [in this issue]. + + +## Usage + + +Inside your `Program.fs` File find the `App` class and be sure to set the name of your Application +```fsharp +type MainWindow() as this = (*... code ... *) + +type App() = + inherit Application() + + override this.Initialize() = + this.Styles.Load "avares://Avalonia.Themes.Default/DefaultTheme.xaml" + this.Styles.Load "avares://Avalonia.Themes.Default/Accents/BaseDark.xaml" + + // 🚩name visible in native menu + this.Name <- "Counter App" + + override this.OnFrameworkInitializationCompleted() = + match this.ApplicationLifetime with + | :? IClassicDesktopStyleApplicationLifetime as desktopLifetime -> + desktopLifetime.MainWindow <- MainWindow() + | _ -> () +``` + +then just create a new NativeMenu +```fsharp +type MainWindow() as this = + inherit HostWindow() + do + base.Title <- "Counter Example" + base.Height <- 400.0 + base.Width <- 400.0 + + // 🚩create menu and menu items + let incrementItem = NativeMenuItem "Increment" + let decrementItem = NativeMenuItem "Decrement" + + let editCounterItem = NativeMenuItem "Edit Counter" + let editCounterMenu = NativeMenu() + editCounterItem.Menu <- editCounterMenu + editCounterMenu.Add incrementItem + editCounterMenu.Add decrementItem + + let nativeMenu = NativeMenu() + nativeMenu.Add editCounterItem + + // 🚩set menu + NativeMenu.SetMenu(this, nativeMenu) + + Elmish.Program.mkSimple (fun () -> Counter.init) Counter.update Counter.view + |> Program.withHost this + |> Program.withConsoleTrace + |> Program.run +``` +and that is enough to show your native menu, if you want to interact with the contents of your menu (the most likely scenario) you will need to add some subscriptions to hook up with your Elmish program + +```fsharp +// 🚩hook menu actions in Elmish +let menuSub (_state: Counter.State) = + let sub (dispatch: Counter.Msg -> unit) = + incrementItem.Clicked.Add (fun _ -> dispatch Counter.Msg.Increment) + decrementItem.Clicked.Add (fun _ -> dispatch Counter.Msg.Decrement) + () + Cmd.ofSub sub + +Elmish.Program.mkSimple (fun () -> Counter.init) Counter.update Counter.view +|> Program.withHost this +// 🚩 use menu subscription +|> Program.withSubscription menuSub + +|> Program.withConsoleTrace +|> Program.run +``` \ No newline at end of file From 71fff38f0e7211d046c68d3a83cf83055d149f70 Mon Sep 17 00:00:00 2001 From: Angel Daniel Munoz Gonzalez Date: Sun, 19 Apr 2020 18:21:45 -0600 Subject: [PATCH 5/8] add menu --- src/docs/controls/Menu.md | 102 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/docs/controls/Menu.md diff --git a/src/docs/controls/Menu.md b/src/docs/controls/Menu.md new file mode 100644 index 0000000..93fa808 --- /dev/null +++ b/src/docs/controls/Menu.md @@ -0,0 +1,102 @@ +--- +layout: control +name: Menu +group: controls +--- +[Menu API]: https://avaloniaui.net/api/Avalonia.Controls/Menu/ +[Menu.fs]: https://github.com/AvaloniaCommunity/Avalonia.FuncUI/blob/master/src/Avalonia.FuncUI.DSL/Menu.fs +[Menu]: http://avaloniaui.net/docs/controls/menu +[Image]: http://avaloniaui.net/docs/controls/image +[sample]: https://github.com/AvaloniaCommunity/Avalonia.FuncUI/blob/master/src/Examples/Examples.MusicPlayer/Shell.fs#L162 +[this file]: https://github.com/AvaloniaCommunity/Avalonia.FuncUI/blob/master/src/Examples/Examples.MusicPlayer/Extensions.fs#L5 + +> *Note*: You can check the Avalonia docs for the [Menu API] and [Menu] if you need more information. +> +> For Avalonia.FuncUI's DSL properties you can check [Menu.fs] + +The menu control allows you to add a list of buttons in a horizontal manner which supports sub-items, it's usually put at the top of the application inside a DockPanel, but it can be placed anywhere in the application. + + +## Usage + +**Top-Level Menu Items** + +To create top-level navigation menus you just need to provide a list of `MenuItem` controls and use the `.viewItems` property on the [Menu] control +```fsharp +let menuItems = [ + MenuItem.Create [ + MenuItem.header "File" + ] + MenuItem.Create [ + MenuItem.header "Edit" + ] +] + +Menu.create [ + Menu.viewItems menuItems +] +``` + +**Set Sub-Menus** + +Each MenuItem can contain MenuItems themselves if you need a sub-menu you just need to provide the appropriate children +```fsharp +let fileItems = [ + MenuItem.Create [ + MenuItem.header "Open File" + ] + MenuItem.Create [ + MenuItem.header "Open Folder" + ] +] + +let menuItems = [ + MenuItem.Create [ + MenuItem.header "Files" + MenuItem.viewItems fleItems + ] + MenuItem.Create [ + MenuItem.header "Preferences" + ] +] + +Menu.create [ + Menu.viewItems menuItems +] +``` + +**Set Icons** + +To set Icons to the menu item you just need to provide an [Image] +you can check this [sample] which uses an extension method defined in [this file] + +```fsharp +let icon = (* obtain an Image instance *) +let menuItems = [ + MenuItem.Create [ + MenuItem.header "Files" + MenuItem.icon icon + ] + MenuItem.Create [ + MenuItem.header "Preferences" + ] +] + +Menu.create [ + Menu.viewItems menuItems +] +``` + +**Dispatch Actions From Menu Items** +```fsharp +let menuItems = [ + MenuItem.Create [ + MenuItem.header "About" + MenuItem.onClick(fun _ -> dispatch GoToAbout) + ] +] + +Menu.create [ + Menu.viewItems menuItems +] +``` \ No newline at end of file From 08874b097882530b9a5056b5bb0e5c8355518841 Mon Sep 17 00:00:00 2001 From: Angel Daniel Munoz Gonzalez Date: Sun, 19 Apr 2020 18:48:48 -0600 Subject: [PATCH 6/8] add tabs --- src/docs/controls/Tabs.md | 84 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/docs/controls/Tabs.md diff --git a/src/docs/controls/Tabs.md b/src/docs/controls/Tabs.md new file mode 100644 index 0000000..c7c8604 --- /dev/null +++ b/src/docs/controls/Tabs.md @@ -0,0 +1,84 @@ +--- +layout: control +name: TabControl +group: controls +--- +[TabControl API]: https://avaloniaui.net/api/Avalonia.Controls/TabControl/ +[TabControl.fs]: https://github.com/AvaloniaCommunity/Avalonia.FuncUI/blob/master/src/Avalonia.FuncUI.DSL/TabControl.fs +[TabControl]: http://avaloniaui.net/docs/controls/tabcontrol +[example]: https://github.com/AvaloniaCommunity/Avalonia.FuncUI/blob/master/src/Avalonia.FuncUI.ControlCatalog/Views/MainView.fs +[ViewBuilder]: https://github.com/AvaloniaCommunity/Avalonia.FuncUI/blob/master/src/Avalonia.FuncUI.ControlCatalog/Views/MainView.fs#L36 +[HostControl]: controls/HostControl.html + +> *Note*: You can check the Avalonia docs for the [TabControl API] and [TabControl] if you need more information. +> +> For Avalonia.FuncUI's DSL properties you can check [TabControl.fs] + +The [TabControl] offers you a way to present content inside your application, each tab contains a different set of controls. + + + +**Set Tabs** +```fsharp +let homePageContent = DockPanel.create [ TextBox.create [ TextBox.text "Home" ] ] +let aboutPageContent = DockPanel.create [ TextBox.create [ TextBox.text "About" ] ] + +let tabs = [ + TabItem.create [ + TabItem.header "Home" + TabItem.content homePageContent + ] + TabItem.create [ + TabItem.header "About" + TabItem.content aboutPageContent + ] +] + +TabControl.create [ + TabControl.tabStripPlacement Dock.Left // Change this property to tell the app where to show the tab bar + TabControl.viewItems tabs +] +``` + +**Set HostControl as content** + +You can also include individual Elmish Controls as the content of your tabs by using the [ViewBuilder] +visit the [example] to see it in action + +```fsharp +// counter.fs +module Counter = + type State = (* state definition *) + type Msg = (* message definition *) + let init = (* init definition *) + let update state msg = (* update definition *) + let view state dispatch = (* view definition *) + + // encapsule the Elmish architecture in this Host Control + type Host() as this = + inherit HostControl() + do + Elmish.Program.mkSimple (fun () -> init) update view + |> Program.withHost this + |> Program.run + +// Program.fs +```fsharp +let aboutPageContent = DockPanel.create [ TextBox.create [ TextBox.text "About" ] ] +let tabs = [ + TabItem.create [ + TabItem.header "Counter" + // use the ViewBuilder to be able to use the Counter module in a stand alone + TabItem.content (ViewBuilder.Create([])) + ] + TabItem.create [ + TabItem.header "About" + TabItem.content aboutPageContent + ] +] + +TabControl.create [ + TabControl.viewItems tabs +] +``` +In the example above the `Counter` module defines a [HostControl] to allow that module to work by itself this means you don't need to nest every view/control inside the main Elmish module of your app this can help you to reduce boilerplate and to reduce complexity in the main module of your application \ No newline at end of file From d40f5443c28d9822884611079bada3247e48d9a5 Mon Sep 17 00:00:00 2001 From: Angel Daniel Munoz Gonzalez Date: Sun, 19 Apr 2020 18:48:56 -0600 Subject: [PATCH 7/8] update readme links --- src/docs/README.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/docs/README.markdown b/src/docs/README.markdown index 15e8959..e15c43a 100644 --- a/src/docs/README.markdown +++ b/src/docs/README.markdown @@ -1,8 +1,8 @@ # Docs [Fornax]: https://github.com/ionide/Fornax -[guides]: -[posts]: -[controls] +[guides]:./guides +[posts]: ./posts +[controls]: ./controls The docs website is built using [Fornax], to contribute to the docs you can add a new (or update an existing) markdown file in the [guides], [posts], [controls] directories. From 670e6325ef8809172ce22a150acdb7e703eae93a Mon Sep 17 00:00:00 2001 From: Angel Daniel Munoz Gonzalez Date: Sun, 19 Apr 2020 18:50:21 -0600 Subject: [PATCH 8/8] update docs to latest content --- docs/about.html | 30 ++ docs/blog.html | 30 ++ docs/controls/Border.html | 30 ++ docs/controls/Button.html | 30 ++ docs/controls/CheckBox.html | 30 ++ docs/controls/DatePicker.html | 30 ++ docs/controls/Menu.html | 328 ++++++++++++++++++ docs/controls/NativeMenu.html | 323 +++++++++++++++++ docs/controls/Tabs.html | 316 +++++++++++++++++ docs/guides.html | 30 ++ docs/guides/Basic-Template.html | 30 ++ docs/guides/Full-Template.html | 30 ++ docs/guides/Home.html | 30 ++ docs/guides/Quickstart-Template.html | 30 ++ .../Unit-Testing-Avalonia-FuncUI-Apps.html | 30 ++ docs/guides/Views-and-Attributes.html | 30 ++ docs/index.html | 30 ++ docs/release-notes.html | 30 ++ src/docs/loaders/globalloader.fsx | 2 +- 19 files changed, 1418 insertions(+), 1 deletion(-) create mode 100644 docs/controls/Menu.html create mode 100644 docs/controls/NativeMenu.html create mode 100644 docs/controls/Tabs.html diff --git a/docs/about.html b/docs/about.html index f689e2b..69a8d6c 100644 --- a/docs/about.html +++ b/docs/about.html @@ -111,6 +111,21 @@ DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • @@ -246,6 +261,21 @@

    DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • diff --git a/docs/blog.html b/docs/blog.html index 45a39ed..fc5e4cb 100644 --- a/docs/blog.html +++ b/docs/blog.html @@ -111,6 +111,21 @@ DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • @@ -195,6 +210,21 @@

    DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • diff --git a/docs/controls/Border.html b/docs/controls/Border.html index 131238a..30eab41 100644 --- a/docs/controls/Border.html +++ b/docs/controls/Border.html @@ -111,6 +111,21 @@ DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • @@ -278,6 +293,21 @@

    Usage

    DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • diff --git a/docs/controls/Button.html b/docs/controls/Button.html index 9fdd858..502d6e3 100644 --- a/docs/controls/Button.html +++ b/docs/controls/Button.html @@ -111,6 +111,21 @@ DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • @@ -249,6 +264,21 @@

    Usage

    DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • diff --git a/docs/controls/CheckBox.html b/docs/controls/CheckBox.html index 6024702..d2eac45 100644 --- a/docs/controls/CheckBox.html +++ b/docs/controls/CheckBox.html @@ -111,6 +111,21 @@ DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • @@ -249,6 +264,21 @@

    Usage

    DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • diff --git a/docs/controls/DatePicker.html b/docs/controls/DatePicker.html index 9d985c3..7e27cb0 100644 --- a/docs/controls/DatePicker.html +++ b/docs/controls/DatePicker.html @@ -111,6 +111,21 @@ DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • @@ -255,6 +270,21 @@

    Usage

    DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • diff --git a/docs/controls/Menu.html b/docs/controls/Menu.html new file mode 100644 index 0000000..25a1efc --- /dev/null +++ b/docs/controls/Menu.html @@ -0,0 +1,328 @@ + + + + + + Avalonia.FuncUI + + + + + + + + + + + +
    + +
    +
    +
    +

    + Menu +

    +

    + Controls +

    +
    +
    +
    +

    Note: You can check the Avalonia docs for the Menu API and Menu if you need more information.

    +

    For Avalonia.FuncUI's DSL properties you can check Menu.fs

    +
    +

    The menu control allows you to add a list of buttons in a horizontal manner which supports sub-items, it's usually put at the top of the application inside a DockPanel, but it can be placed anywhere in the application.

    +

    Usage

    +

    Top-Level Menu Items

    +

    To create top-level navigation menus you just need to provide a list of MenuItem controls and use the .viewItems property on the Menu control

    +
    let menuItems = [
    +    MenuItem.Create [
    +        MenuItem.header "File"
    +    ]
    +    MenuItem.Create [
    +        MenuItem.header "Edit"
    +    ]
    +]
    +
    +Menu.create [
    +  Menu.viewItems menuItems
    +]
    +
    +

    Set Sub-Menus

    +

    Each MenuItem can contain MenuItems themselves if you need a sub-menu you just need to provide the appropriate children

    +
    let fileItems = [
    +  MenuItem.Create [
    +        MenuItem.header "Open File"
    +  ]
    +  MenuItem.Create [
    +      MenuItem.header "Open Folder"
    +  ]
    +]
    +
    +let menuItems = [
    +    MenuItem.Create [
    +        MenuItem.header "Files"
    +        MenuItem.viewItems fleItems
    +    ]
    +    MenuItem.Create [
    +        MenuItem.header "Preferences"
    +    ]
    +]
    +
    +Menu.create [
    +  Menu.viewItems menuItems
    +]
    +
    +

    Set Icons

    +

    To set Icons to the menu item you just need to provide an Image +you can check this sample which uses an extension method defined in this file

    +
    let icon = (* obtain an Image instance *)
    +let menuItems = [
    +    MenuItem.Create [
    +        MenuItem.header "Files"
    +        MenuItem.icon icon
    +    ]
    +    MenuItem.Create [
    +        MenuItem.header "Preferences"
    +    ]
    +]
    +
    +Menu.create [
    +  Menu.viewItems menuItems
    +]
    +
    +

    Dispatch Actions From Menu Items

    +
    let menuItems = [
    +    MenuItem.Create [
    +        MenuItem.header "About"
    +        MenuItem.onClick(fun _ -> dispatch GoToAbout)
    +    ]
    +]
    +
    +Menu.create [
    +  Menu.viewItems menuItems
    +]
    +
    + +
    +
    +
    +
    + + + + + + \ No newline at end of file diff --git a/docs/controls/NativeMenu.html b/docs/controls/NativeMenu.html new file mode 100644 index 0000000..d974e86 --- /dev/null +++ b/docs/controls/NativeMenu.html @@ -0,0 +1,323 @@ + + + + + + Avalonia.FuncUI + + + + + + + + + + + +
    + +
    +
    +
    +

    + NativeMenu +

    +

    + Controls +

    +
    +
    +
    +

    You can check the NativeMenu Avalonia docs for more information

    +
    +

    Native menus were introduced in Avalonia in version 0.9.0 you can check the announcement to see a brief explanation on how to use them in Avalonia Applications.

    +

    Currently for Avalonia.FuncUI there is not a DSL and the NativeMenu control is in a weird spot for Avalonia.FuncUI since this control works directly on the main Application/Window object so it's though to pull a DSL on top of that. But! thankfully you can just use plain F# for the menu as noted in this issue.

    +

    Usage

    +

    Inside your Program.fs File find the App class and be sure to set the name of your Application

    +
    type MainWindow() as this = (*... code ... *)
    +
    +type App() =
    +    inherit Application()
    +
    +    override this.Initialize() =
    +        this.Styles.Load "avares://Avalonia.Themes.Default/DefaultTheme.xaml"
    +        this.Styles.Load "avares://Avalonia.Themes.Default/Accents/BaseDark.xaml"
    +
    +        // 🚩name visible in native menu
    +        this.Name <- "Counter App"
    +
    +    override this.OnFrameworkInitializationCompleted() =
    +        match this.ApplicationLifetime with
    +        | :? IClassicDesktopStyleApplicationLifetime as desktopLifetime ->
    +            desktopLifetime.MainWindow <- MainWindow()
    +        | _ -> ()
    +
    +

    then just create a new NativeMenu

    +
    type MainWindow() as this = 
    +    inherit HostWindow()
    +    do
    +        base.Title <- "Counter Example"
    +        base.Height <- 400.0
    +        base.Width <- 400.0
    +
    +        // 🚩create menu and menu items
    +        let incrementItem = NativeMenuItem "Increment"
    +        let decrementItem = NativeMenuItem "Decrement"
    +        
    +        let editCounterItem = NativeMenuItem "Edit Counter"
    +        let editCounterMenu =  NativeMenu()
    +        editCounterItem.Menu <- editCounterMenu
    +        editCounterMenu.Add incrementItem
    +        editCounterMenu.Add decrementItem
    +        
    +        let nativeMenu = NativeMenu()
    +        nativeMenu.Add editCounterItem
    +        
    +        // 🚩set menu
    +        NativeMenu.SetMenu(this, nativeMenu)
    +
    +        Elmish.Program.mkSimple (fun () -> Counter.init) Counter.update Counter.view
    +        |> Program.withHost this
    +        |> Program.withConsoleTrace
    +        |> Program.run
    +
    +

    and that is enough to show your native menu, if you want to interact with the contents of your menu (the most likely scenario) you will need to add some subscriptions to hook up with your Elmish program

    +
    // 🚩hook menu actions in Elmish
    +let menuSub (_state: Counter.State) =
    +    let sub (dispatch: Counter.Msg -> unit) =
    +        incrementItem.Clicked.Add (fun _ -> dispatch Counter.Msg.Increment)
    +        decrementItem.Clicked.Add (fun _ -> dispatch Counter.Msg.Decrement)
    +        ()
    +    Cmd.ofSub sub
    +
    +Elmish.Program.mkSimple (fun () -> Counter.init) Counter.update Counter.view
    +|> Program.withHost this
    +// 🚩 use menu subscription
    +|> Program.withSubscription menuSub
    +
    +|> Program.withConsoleTrace
    +|> Program.run
    +
    + +
    +
    +
    +
    + + + + + + \ No newline at end of file diff --git a/docs/controls/Tabs.html b/docs/controls/Tabs.html new file mode 100644 index 0000000..baf4a4c --- /dev/null +++ b/docs/controls/Tabs.html @@ -0,0 +1,316 @@ + + + + + + Avalonia.FuncUI + + + + + + + + + + + +
    + +
    +
    +
    +

    + TabControl +

    +

    + Controls +

    +
    +
    +
    +

    Note: You can check the Avalonia docs for the TabControl API and TabControl if you need more information.

    +

    For Avalonia.FuncUI's DSL properties you can check TabControl.fs

    +
    +

    The TabControl offers you a way to present content inside your application, each tab contains a different set of controls.

    +

    Set Tabs

    +
    let homePageContent = DockPanel.create [ TextBox.create [ TextBox.text "Home" ] ]
    +let aboutPageContent = DockPanel.create [ TextBox.create [ TextBox.text "About" ] ]
    +
    +let tabs = [
    +    TabItem.create [
    +        TabItem.header "Home"
    +        TabItem.content homePageContent
    +    ]
    +    TabItem.create [
    +        TabItem.header "About"
    +        TabItem.content aboutPageContent
    +    ]
    +]
    +
    +TabControl.create [
    +    TabControl.tabStripPlacement Dock.Left // Change this property to tell the app where to show the tab bar
    +    TabControl.viewItems tabs
    +]
    +
    +

    Set HostControl as content

    +

    You can also include individual Elmish Controls as the content of your tabs by using the ViewBuilder +visit the example to see it in action

    +
    // counter.fs
    +module Counter = 
    +    type State = (* state definition *)
    +    type Msg = (* message definition *)
    +    let init = (* init definition *)
    +    let update state msg = (* update definition *)
    +    let view state dispatch = (* view definition *)
    +
    +    // encapsule the Elmish architecture in this Host Control
    +    type Host() as this =
    +        inherit HostControl()
    +        do
    +            Elmish.Program.mkSimple (fun () -> init) update view
    +            |> Program.withHost this
    +            |> Program.run
    +
    +// Program.fs
    +```fsharp
    +let aboutPageContent = DockPanel.create [ TextBox.create [ TextBox.text "About" ] ]
    +let tabs = [
    +    TabItem.create [
    +        TabItem.header "Counter"
    +        // use the ViewBuilder to be able to use the Counter module in a stand alone
    +        TabItem.content (ViewBuilder.Create<Counter.Host>([]))
    +    ]
    +    TabItem.create [
    +        TabItem.header "About"
    +        TabItem.content aboutPageContent
    +    ]
    +]
    +
    +TabControl.create [
    +    TabControl.viewItems tabs
    +]
    +
    +

    In the example above the Counter module defines a HostControl to allow that module to work by itself this means you don't need to nest every view/control inside the main Elmish module of your app this can help you to reduce boilerplate and to reduce complexity in the main module of your application

    + +
    +
    +
    +
    + + + + + + \ No newline at end of file diff --git a/docs/guides.html b/docs/guides.html index b63ee5c..aaf8f96 100644 --- a/docs/guides.html +++ b/docs/guides.html @@ -111,6 +111,21 @@ DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • @@ -232,6 +247,21 @@

    DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • diff --git a/docs/guides/Basic-Template.html b/docs/guides/Basic-Template.html index bc01fc8..65ea762 100644 --- a/docs/guides/Basic-Template.html +++ b/docs/guides/Basic-Template.html @@ -111,6 +111,21 @@ DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • @@ -350,6 +365,21 @@

    View

    DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • diff --git a/docs/guides/Full-Template.html b/docs/guides/Full-Template.html index ae4c614..1228675 100644 --- a/docs/guides/Full-Template.html +++ b/docs/guides/Full-Template.html @@ -111,6 +111,21 @@ DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • @@ -337,6 +352,21 @@

    Styles

    DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • diff --git a/docs/guides/Home.html b/docs/guides/Home.html index 4d9765f..59569d8 100644 --- a/docs/guides/Home.html +++ b/docs/guides/Home.html @@ -111,6 +111,21 @@ DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • @@ -220,6 +235,21 @@

    Setup

    DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • diff --git a/docs/guides/Quickstart-Template.html b/docs/guides/Quickstart-Template.html index cb7df9d..2bb8539 100644 --- a/docs/guides/Quickstart-Template.html +++ b/docs/guides/Quickstart-Template.html @@ -111,6 +111,21 @@ DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • @@ -339,6 +354,21 @@

    ProjectName.Core.Tests

    DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • diff --git a/docs/guides/Unit-Testing-Avalonia-FuncUI-Apps.html b/docs/guides/Unit-Testing-Avalonia-FuncUI-Apps.html index e087389..690d87b 100644 --- a/docs/guides/Unit-Testing-Avalonia-FuncUI-Apps.html +++ b/docs/guides/Unit-Testing-Avalonia-FuncUI-Apps.html @@ -111,6 +111,21 @@ DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • @@ -417,6 +432,21 @@

    DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • diff --git a/docs/guides/Views-and-Attributes.html b/docs/guides/Views-and-Attributes.html index 75a3f25..aaf0e0a 100644 --- a/docs/guides/Views-and-Attributes.html +++ b/docs/guides/Views-and-Attributes.html @@ -111,6 +111,21 @@ DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • @@ -316,6 +331,21 @@

    📦 Content Properties

    DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • diff --git a/docs/index.html b/docs/index.html index dd9f968..5d3d3a4 100644 --- a/docs/index.html +++ b/docs/index.html @@ -111,6 +111,21 @@ DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • @@ -250,6 +265,21 @@

    Example

    DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • diff --git a/docs/release-notes.html b/docs/release-notes.html index 033942f..a380af9 100644 --- a/docs/release-notes.html +++ b/docs/release-notes.html @@ -111,6 +111,21 @@ DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • @@ -195,6 +210,21 @@

    DatePicker +
  • + + Menu + +
  • +
  • + + NativeMenu + +
  • +
  • + + TabControl + +
  • diff --git a/src/docs/loaders/globalloader.fsx b/src/docs/loaders/globalloader.fsx index bb328ea..638c88d 100644 --- a/src/docs/loaders/globalloader.fsx +++ b/src/docs/loaders/globalloader.fsx @@ -12,6 +12,6 @@ let loader (projectRoot: string) (siteContent: SiteContents) = { title = "Avalonia.FuncUI" description = "Avalonia.FuncUI Website" showSideBar = true - baseUrl = "/Avalonia.FuncUI.Docs/" + baseUrl = "/Avalonia.FuncUI.Docs/" }) siteContent