如何使用 Elm(最好是 Elm-UI)制作可拖动的拆分面板?

How can I make a draggable split panel with Elm (preferably Elm-UI)?

我在寻找 Elm 的拆分窗格,发现一些东西似乎不太好用。

doodledood/elm-split-pane 与 Elm 0.19.1 不兼容,所以我想我要么必须切换到 Elm 0.18,要么将其转换为 Elm 0.19.1 才能使用它。它也是根据 Elm HTML 定义的,而不是 Elm UI.

我也找到了https://github.com/ellie-app/ellie/blob/master/assets/src/Ellie/Ui/SplitPane.elm。这涉及到一堆自定义 JavaScript,它与应用程序的其余部分的耦合程度对我来说并不明显。

我在我的 elm 项目中使用 elm-ui 的拆分窗格。我使用了这个 SplitPane elm-html-based 包 https://github.com/doodledood/elm-split-pane,将它的源代码复制到我的项目中,并通过包装内容和属性将其内部结构转换为来自其视图函数的 return 元素消息。

转换函数的源代码如下:

view : ViewConfig msg -> Element msg -> Element msg -> State -> Element msg
view (ViewConfig viewConfig) firstView secondView (State state) =
    let
        splitter =
            getConcreteSplitter viewConfig state.orientation state.dragState
    in
    case state.orientation of
        Horizontal ->
            row
                (paneContainerStyle state.orientation
                    ++ [ width fill ]
                )
                [ el (firstChildViewStyle (State state)) firstView
                , splitter
                , el (secondChildViewStyle (State state)) secondView
                ]

        Vertical ->
            column (paneContainerStyle state.orientation)
                [ el (firstChildViewStyle (State state)) firstView
                , splitter
                , el (secondChildViewStyle (State state)) secondView
                ]


viewReversed : ViewConfig msg -> Element msg -> Element msg -> State -> Element msg
viewReversed (ViewConfig viewConfig) firstView secondView (State state) =
    let
        splitter =
            getConcreteSplitter viewConfig state.orientation state.dragState
    in
    case state.orientation of
        Horizontal ->
            row
                (paneContainerStyle state.orientation
                    ++ [ width fill ]
                )
                [ el (secondChildViewStyle (State state)) secondView
                , splitter
                , el (firstChildViewStyle (State state)) firstView
                ]

        Vertical ->
            column (paneContainerStyle state.orientation)
                [ el (secondChildViewStyle (State state)) firstView
                , splitter
                , el (firstChildViewStyle (State state)) firstView
                ]


getConcreteSplitter :
    { toMsg : Msg -> msg
    , splitter : Maybe (CustomSplitter msg)
    }
    -> Orientation
    -> DragState
    -> Element msg
getConcreteSplitter viewConfig orientation4 dragState =
    case viewConfig.splitter of
        Just (CustomSplitter splitter) ->
            splitter

        Nothing ->
            case createCustomSplitter viewConfig.toMsg <| createDefaultSplitterDetails orientation4 dragState of
                CustomSplitter defaultSplitter ->
                    defaultSplitter

以下是样式函数:

paneContainerStyle : Orientation -> List (Attribute msg)
paneContainerStyle orientation5 =
    [ style "overflow" "hidden"
    , style "display" "flex"
    , style "flexDirection"
        (case orientation5 of
            Horizontal ->
                "row"

            Vertical ->
                "column"
        )
    , style "justifyContent" "center"
    , style "alignItems" "center"
    , style "width" "100%"
    , style "height" "100%"
    , style "boxSizing" "border-box"
    ]
        |> List.map Element.htmlAttribute


firstChildViewStyle : State -> List (Attribute msg)
firstChildViewStyle (State state) =
    case state.splitterPosition of
        Px px2 ->
            let
                v =
                    (String.fromFloat <| toFloat (getValue px2)) ++ "px"
            in
            case state.orientation of
                Horizontal ->
                    [ style "display" "flex"
                    , style "width" v
                    , style "height" "100%"
                    , style "overflow" "hidden"
                    , style "boxSizing" "border-box"
                    , style "position" "relative"
                    ]
                        |> List.map Element.htmlAttribute

                Vertical ->
                    [ style "display" "flex"
                    , style "width" "100%"
                    , style "height" v
                    , style "overflow" "hidden"
                    , style "boxSizing" "border-box"
                    , style "position" "relative"
                    ]
                        |> List.map Element.htmlAttribute

        Percentage p ->
            let
                v =
                    String.fromFloat <| getValue p
            in
            [ style "display" "flex"
            , style "flex" v
            , style "width" "100%"
            , style "height" "100%" -- pz edit
            , style "overflow" "hidden"
            , style "boxSizing" "border-box"
            , style "position" "relative"
            ]
                |> List.map Element.htmlAttribute


secondChildViewStyle : State -> List (Attribute msg)
secondChildViewStyle (State state) =
    case state.splitterPosition of
        Px _ ->
            [ style "display" "flex"
            , style "flex" "1"
            , style "width" "100%"
            , style "height" "100%"
            , style "overflow" "hidden"
            , style "boxSizing" "border-box"
            , style "position" "relative"
            ]
                |> List.map Element.htmlAttribute

        Percentage p ->
            let
                v =
                    String.fromFloat <| 1 - getValue p
            in
            [ style "display" "flex"
            , style "flex" v
            , style "width" "100%"
            , style "height" "100%"
            , style "overflow" "hidden"
            , style "boxSizing" "border-box"
            , style "position" "relative"
            ]
                |> List.map Element.htmlAttribute

乌里

我尝试过这种方法,但个人发现 html 和 elm-ui 的组合非常混乱。 (这就是我,YMMV。)我更简单的方法是使用带有自定义拇指的 elm-ui 滚动条。

contentArea : Model -> Element Msg
contentArea model =
    let
        leftPane =
            column
                [ width fill, alignTop ]
                [ ... ]

        rightPane =
                column [ spacing 5, padding 5, alignTop, centerX ]
                    [ ... ]

        splitter =
            Input.slider
                [ height <| px 2
                , width <| px 1000
                , centerY
                , behindContent <|
                    -- Slider track
                    el
                        [ width fill
                        , height <| px 1
                        , centerY
                        , centerX
                        ]
                        none
                ]
                { onChange = ResizeViews << round
                , label =
                    Input.labelHidden "Splitter"
                , min = 0.0
                , max = toFloat 1000
                , step = Just 1
                , value = toFloat model.splitInPixels
                , thumb = customThumb
                }

        customThumb =
            Input.thumb
                [ width (px 3)
                , height (px 600)
                , moveDown 300
                , Border.rounded 1
                , Border.width 1
                , Border.color (rgb 0.5 0.5 0.5)
                , Background.color FlatColors.BritishPalette.seabrook
                ]
    in
    column [ width fill, spacing 5, padding 5 ]
        [ splitter
        , row [ width fill, spacing 5, padding 5 ]
            [ el [ width <| fillPortion model.splitInPixels, alignTop ] leftPane
            , el [ width <| fillPortion (1000 - model.splitInPixels), alignTop ] rightPane
            ]
        ]