fsprojects / Avalonia.FuncUI

Develop cross-plattform GUI Applications using F# and Avalonia!
https://funcui.avaloniaui.net/
MIT License
889 stars 72 forks source link

Consider extending the DSL for more convenient ColumnDefinitions and RowDefinitions #365

Closed beyon closed 7 months ago

beyon commented 8 months ago

Motivation/Background:

Setting minWidth and/or minHeight on the "cells" directely of a Grid doesn't work in Avalonia, at least not currently (see: https://github.com/AvaloniaUI/Avalonia/issues/7637).

You can't specify minHeight and minHeight when specifying rowDefinition/columnDefinition in string form, instead you have to use ColumnDefinitions and RowDefinitions objects instead.

Using the ColumnDefinitions, RowDefinitions, ColumnDefinition, RowDefinition objects in FuncUI results in verbose code that doesn't fit so nicely with the FuncUI DSL.

It's also nice to not have an alternative to specifying the definitions in a string with no validation.

I ended up writing my own DSL code when working around the minimum size issue and made a demo project: https://github.com/beyon/FuncUIGridCellMinWidthAndHeight

More information is available in the demo project readme file.

Current way of working with ColumnDefinitions and RowDefinitions:

    //Column definitions
    let col1 = ColumnDefinition()
    let col2 = ColumnDefinition()
    let col3 = ColumnDefinition()
    col1.Width <- GridLength(1.0, GridUnitType.Star)
    col1.MinWidth <- 64.0
    col2.Width <- GridLength(4.0, GridUnitType.Pixel)
    col2.MinWidth <- 4.0
    col3.Width <- GridLength(3.0, GridUnitType.Star)
    col3.MinWidth <- 64.0
    let colDefs = ColumnDefinitions()
    colDefs.Add(col1)
    colDefs.Add(col2)
    colDefs.Add(col3)
    //Row definitions
    let row1 = RowDefinition()
    let row2 = RowDefinition()
    let row3 = RowDefinition()
    row1.Height <- GridLength(1.0, GridUnitType.Star)
    row1.MinHeight <- 64.0
    row2.Height <- GridLength(4.0, GridUnitType.Pixel)
    row2.MinHeight <- 4.0
    row3.Height <- GridLength(1.5, GridUnitType.Star)
    row3.MinHeight <- 64.0
    let rowDefs = RowDefinitions()
    rowDefs.Add(row1)
    rowDefs.Add(row2)
    rowDefs.Add(row3)
    Grid.create [
        Grid.columnDefinitions colDefs
        Grid.rowDefinitions rowDefs
        Grid.children [
                ...

Possible solution

Add extensions to the DSL that makes it nicer to work with ColumnDefinitions and RowDefinitions.

Code using DSL with Grid.columnDefinition.create and Grid.rowDefinitons.create:

Grid.create [
    Grid.columnDefinitions [
        ColumnDefinition.create(GridLength(1.0, GridUnitType.Star), minWidth = 64.)
        ColumnDefinition.create(GridLength(4.0, GridUnitType.Pixel), minWidth = 4.)
        ColumnDefinition.create(GridLength(3.0, GridUnitType.Star), minWidth = 64.)
    ]
    Grid.rowDefinitions [
        ColumnDefinition.create(GridLength(1.0, GridUnitType.Star), minHeight = 64.)
        ColumnDefinition.create(GridLength(4.0, GridUnitType.Pixel), minHeight = 4.)
        ColumnDefinition.create(GridLength(1.5, GridUnitType.Star), minHeight = 64.)
    ]
    Grid.children [
        ...

Code using DSL with shorthand forms for the different types of rows/columns (my preference):

Grid.create [
    Grid.columnDefinitions [
        ColumnDefinition.createStar(1.0, minWidth=64.0)
        ColumnDefinition.createPixel(4.0, minWidth=4.0)
        ColumnDefinition.createStar(3.0, minWidth=64.0)
    ]
    Grid.rowDefinitions [
        RowDefinition.createStar(1.0, minHeight=64.0)
        RowDefinition.createPixel(4.0, minHeight=4.0)
        RowDefinition.createStar(1.5, minHeight=64.0)
    ]
    Grid.children [
        ...

I tried to align the DSL extensions with the existing DSL but I am sure others might want it to look differently. If you think it's a good idea am happy to make a pull request, but I figure it's best to discuss how and if this should be added first.

JaggerJo commented 8 months ago

Would be happy to merge something like this.

RowDefinition.createPixel(4.0, minHeight=4.0)
RowDefinition.createStar(1.5, minHeight=64.0)

Instead of having 2 different functions I'd prefer this:

RowDefinition.create(GridUnitType.Star, 4.0, minHeight=4.0)
beyon commented 8 months ago

I agree that having just one function (.create(..)) is more in line with the rest of FuncUI.

There are three GridUnitType:s to take into account though and if we stick to using Avalonias GridUnitType as the first parameter there some warts when it comes to dealing with the second value/size/length parameter and if it should be optional or not. Making all the parameters make sense was my reason for having multiple functions.

GridUnitType.Auto : Value isn't used/has no meaning, so it would be nice to not have to specify it like so: .create(GridUnitType.Auto)instead of .create(GridUnitType.Auto, 0.0) or some other dummy value. In favor of value parameter as optional.

GridUnitType.Pixel : Value should always be used, otherwise we have to invent some arbitrary default value. In favor of value as a required parameter

GridUnitType.Star : Value is optional in how it's used in the XAML/string format, leaving it out defaults to 1.0. Slightly in favor of optional value parameter even if having to type 1.0 every time would be that bad.

Main alternatives for function signature in my mind:

  1. Value parameter is optional static member create(unitType: GridUnitType, ?value: float, ?minValue: float, ?maxValue: float) = example usage:
    RowDefinition.create(GridUnitType.Auto)
    RowDefinition.create(GridUnitType.Auto, 0.0) // value doesn't mean anything
    RowDefinition.create(GridUnitType.Auto, minValue=64.0, maxValue=64.0)
    RowDefinition.create(GridUnitType.Pixel) // oops, no pixel dimensions - have to use some magic default value
    RowDefinition.create(GridUnitType.Pixel, 256.0)
    RowDefinition.create(GridUnitType.Pixel, 256.0, minValue=64.0, maxValue=64.0)
    RowDefinition.create(GridUnitType.Star) // value defaults to 1.0 just like when using the string version of .create()
    RowDefinition.create(GridUnitType.Star, 1.5)
    RowDefinition.create(GridUnitType.Star, 1.5, minValue=64.0, maxValue=64.0)
  2. Value parameter is required static member create(unitType: GridUnitType, value: float, ?minValue: float, ?maxValue: float) = example usage:
    RowDefinition.create(GridUnitType.Auto, 0.0) // value doesn't mean anything but has to be provided
    RowDefinition.create(GridUnitType.Auto, 0.0, minValue=64.0, maxValue=64.0)
    RowDefinition.create(GridUnitType.Pixel, 256.0)
    RowDefinition.create(GridUnitType.Pixel, 256.0, minValue=64.0, maxValue=64.0)
    RowDefinition.create(GridUnitType.Star, 1.0) // always have to provide value for .Star also
    RowDefinition.create(GridUnitType.Star, 1.5, minValue=64.0, maxValue=64.0)
  3. Using our own DU instead of Avalonias GridUnitType static member create(unitType: GridCellSize, , ?minValue: float, ?maxValue: float) = Where GridCellSize is:
    [<RequireQualifiedAccess>]
    type GridCellSize=
    | Auto
    | Pixel of float
    | Star of float

    example usage:

    RowDefinition.create(GridCellSize.Auto)
    RowDefinition.create(GridCellSize.Auto, minValue=64.0, maxValue=64.0)
    RowDefinition.create(GridCellSize.Pixel 256.0)
    RowDefinition.create(GridCellSize.Pixel 256.0, minValue=64.0, maxValue=64.0)
    // always have to provide value for .Star. Could make another DU Case or float option but not that nice
    RowDefinition.create(GridCellSize.Star 1.0) 
    RowDefinition.create(GridCellSize.Star 1.5, minValue=64.0, maxValue=64.0)

Let me know which option you would like to see in a pull request or if you have any better ideas or suggested modifications.

JaggerJo commented 8 months ago

Legit point, happy to merge what you come up with

beyon commented 8 months ago

I think I'm inclined to go with 3) (Discriminated Union) if no-one else voices any strong opinions. Will move forward with a pull request when I have the time then.

beyon commented 7 months ago

Pull request created: #371