如何使用 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
]
]
我在寻找 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
]
]