Add support for onChange and model initialization in split

This commit is contained in:
Francisco Vallarino 2021-01-16 20:58:35 -03:00
parent 0f4bfe34ff
commit 2c77ca5427
5 changed files with 128 additions and 37 deletions

View File

@ -60,6 +60,9 @@ handleAppEvent
-> AppEvent
-> [AppEventResponse App AppEvent]
handleAppEvent wenv model evt = case evt of
SliderPos pos -> [Task $ do
print pos
return Nothing]
IncButton -> [Model (model & clickCount %~ (+1)),
Task $ do
threadDelay 1000000
@ -118,7 +121,7 @@ handleAppEvent wenv model evt = case evt of
buildUI :: WidgetEnv App AppEvent -> App -> WidgetNode App AppEvent
buildUI wenv model = trace "Creating UI" widgetHSplit where
widgetHSplit = hsplit_ (image "assets/images/pecans.jpg" `style` [rangeWidth 200 500], widgetTree) [splitIgnoreChildResize False]
widgetHSplit = hsplit (image "assets/images/pecans.jpg" `style` [rangeWidth 200 500], widgetTree)
widgetVSplit = vsplit (image "assets/images/pecans.jpg" `style` [rangeHeight 200 500], widgetTree `style` [rangeHeight 200 500])
mkImg i = vstack [
label ("Image: " <> showt i),

View File

@ -35,7 +35,8 @@ data App = App {
_condition2 :: Bool,
_condition3 :: Bool,
_showAlert :: Bool,
_showConfirm :: Bool
_showConfirm :: Bool,
_splitPos :: Double
} deriving (Eq, Show, Generic, Serialise)
instance Default App where
@ -56,7 +57,8 @@ instance Default App where
_condition2 = False,
_condition3 = False,
_showAlert = False,
_showConfirm = False
_showConfirm = False,
_splitPos = 0.5
}
makeLenses ''App
@ -82,6 +84,7 @@ data AppEvent
| AcceptConfirm
| CancelConfirm
| ChangeTitle Text
| SliderPos Double
| ExitApp
| CancelExitApp
| MaxWindow

View File

@ -61,6 +61,18 @@ numberInBounds (Just minVal) Nothing val = val >= minVal
numberInBounds Nothing (Just maxVal) val = val <= maxVal
numberInBounds (Just minVal) (Just maxVal) val = val >= minVal && val <= maxVal
-- This is meant to be used in coordinate calculations only
doubleCloseTo :: Double -> Double -> Bool
doubleCloseTo val1 val2 = abs (val2 - val1) < 0.0001
doubleInRange :: Double -> Double -> Double -> Bool
doubleInRange minValue maxValue curValue = validMin && validMax where
minDiff = curValue - minValue
maxDiff = maxValue - curValue
-- Some calculations may leave small differences in otherwise valid results
validMin = minDiff >= 0 || abs minDiff < 0.0001
validMax = maxDiff >= 0 || abs maxDiff < 0.0001
isFocusRequest :: WidgetRequest s -> Bool
isFocusRequest MoveFocus{} = True
isFocusRequest SetFocus{} = True

View File

@ -1,19 +1,23 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Monomer.Widgets.Split (
hsplit,
hsplit_,
vsplit,
vsplit_,
splitHandlePos,
splitHandlePosV,
splitHandleSize,
splitIgnoreChildResize
) where
import Codec.Serialise
import Control.Applicative ((<|>))
import Control.Lens ((&), (^.), (.~), (<>~))
import Control.Lens (ALens', (&), (^.), (.~), (<>~))
import Data.Default
import Data.Maybe
import Data.Tuple (swap)
@ -26,32 +30,61 @@ import Monomer.Widgets.Stack (assignStackAreas)
import qualified Monomer.Lens as L
data SplitCfg = SplitCfg {
data SplitCfg s e = SplitCfg {
_spcHandlePos :: Maybe (WidgetData s Double),
_spcHandleSize :: Maybe Double,
_spcIgnoreChildResize :: Maybe Bool
_spcIgnoreChildResize :: Maybe Bool,
_spcOnChange :: [Double -> e],
_spcOnChangeReq :: [WidgetRequest s]
}
instance Default SplitCfg where
instance Default (SplitCfg s e) where
def = SplitCfg {
_spcHandlePos = Nothing,
_spcHandleSize = Nothing,
_spcIgnoreChildResize = Nothing
_spcIgnoreChildResize = Nothing,
_spcOnChange = [],
_spcOnChangeReq = []
}
instance Semigroup SplitCfg where
instance Semigroup (SplitCfg s e) where
(<>) s1 s2 = SplitCfg {
_spcHandlePos = _spcHandlePos s2 <|> _spcHandlePos s1,
_spcHandleSize = _spcHandleSize s2 <|> _spcHandleSize s1,
_spcIgnoreChildResize = _spcIgnoreChildResize s2 <|> _spcIgnoreChildResize s1
_spcIgnoreChildResize = _spcIgnoreChildResize s2 <|> _spcIgnoreChildResize s1,
_spcOnChange = _spcOnChange s2 <|> _spcOnChange s1,
_spcOnChangeReq = _spcOnChangeReq s2 <|> _spcOnChangeReq s1
}
instance Monoid SplitCfg where
instance Monoid (SplitCfg s e) where
mempty = def
splitHandleSize :: Double -> SplitCfg
instance CmbOnChange (SplitCfg s e) Double e where
onChange fn = def {
_spcOnChange = [fn]
}
instance CmbOnChangeReq (SplitCfg s e) s where
onChangeReq req = def {
_spcOnChangeReq = [req]
}
splitHandlePos :: ALens' s Double -> SplitCfg s e
splitHandlePos field = def {
_spcHandlePos = Just (WidgetLens field)
}
splitHandlePosV :: Double -> SplitCfg s e
splitHandlePosV value = def {
_spcHandlePos = Just (WidgetValue value)
}
splitHandleSize :: Double -> SplitCfg s e
splitHandleSize w = def {
_spcHandleSize = Just w
}
splitIgnoreChildResize :: Bool -> SplitCfg
splitIgnoreChildResize :: Bool -> SplitCfg s e
splitIgnoreChildResize ignore = def {
_spcIgnoreChildResize = Just ignore
}
@ -59,7 +92,7 @@ splitIgnoreChildResize ignore = def {
data SplitState = SplitState {
_spsPrevReqs :: (SizeReq, SizeReq),
_spsMaxSize :: Double,
_spsHandleDragged :: Bool,
_spsHandlePosSet :: Bool,
_spsHandlePos :: Double,
_spsHandleRect :: Rect
} deriving (Eq, Show, Generic, Serialise)
@ -67,23 +100,26 @@ data SplitState = SplitState {
hsplit :: (WidgetNode s e, WidgetNode s e) -> WidgetNode s e
hsplit nodes = hsplit_ nodes def
hsplit_ :: (WidgetNode s e, WidgetNode s e) -> [SplitCfg] -> WidgetNode s e
hsplit_ :: (WidgetNode s e, WidgetNode s e) -> [SplitCfg s e] -> WidgetNode s e
hsplit_ nodes configs = split_ True nodes configs
vsplit :: (WidgetNode s e, WidgetNode s e) -> WidgetNode s e
vsplit nodes = vsplit_ nodes def
vsplit_ :: (WidgetNode s e, WidgetNode s e) -> [SplitCfg] -> WidgetNode s e
vsplit_ :: (WidgetNode s e, WidgetNode s e) -> [SplitCfg s e] -> WidgetNode s e
vsplit_ nodes configs = split_ False nodes configs
split_
:: Bool -> (WidgetNode s e, WidgetNode s e) -> [SplitCfg] -> WidgetNode s e
:: Bool
-> (WidgetNode s e, WidgetNode s e)
-> [SplitCfg s e]
-> WidgetNode s e
split_ isHorizontal (node1, node2) configs = newNode where
config = mconcat configs
state = SplitState {
_spsPrevReqs = def,
_spsMaxSize = 0,
_spsHandleDragged = False,
_spsHandlePosSet = False,
_spsHandlePos = 0,
_spsHandleRect = def
}
@ -92,9 +128,10 @@ split_ isHorizontal (node1, node2) configs = newNode where
newNode = defaultWidgetNode widgetName widget
& L.children .~ Seq.fromList [node1, node2]
makeSplit :: Bool -> SplitCfg -> SplitState -> Widget s e
makeSplit :: Bool -> SplitCfg s e -> SplitState -> Widget s e
makeSplit isHorizontal config state = widget where
widget = createContainer state def {
containerInit = init,
containerRestore = restore,
containerHandleEvent = handleEvent,
containerGetSizeReq = getSizeReq,
@ -103,9 +140,27 @@ makeSplit isHorizontal config state = widget where
handleW = fromMaybe 5 (_spcHandleSize config)
init wenv node = result where
useModelValue value = resultWidget newNode where
newState = state {
_spsHandlePosSet = True,
_spsHandlePos = value
}
newNode = node
& L.widget .~ makeSplit isHorizontal config newState
result = case getModelPos wenv config of
Just val
| val >= 0 && val <= 1 -> useModelValue val
_ -> resultWidget node
restore wenv oldState oldNode newNode = result where
oldHandlePos = _spsHandlePos oldState
modelPos = getModelPos wenv config
newState = oldState {
_spsHandlePos = fromMaybe oldHandlePos modelPos
}
result = resultWidget $ newNode
& L.widget .~ makeSplit isHorizontal config oldState
& L.widget .~ makeSplit isHorizontal config newState
handleEvent wenv target evt node = case evt of
Move point
@ -117,18 +172,21 @@ makeSplit isHorizontal config state = widget where
| isHorizontal = (px - ra ^. L.x) / maxSize
| otherwise = (py - ra ^. L.y) / maxSize
newState = state {
_spsHandleDragged = True,
_spsHandlePosSet = True,
_spsHandlePos = newHandlePos
}
tmpNode = node
& L.widget .~ makeSplit isHorizontal config newState
newNode = widgetResize (tmpNode ^. L.widget) wenv vp ra tmpNode
resultDrag = newNode
& L.requests <>~ Seq.fromList [cursorIconReq, RenderOnce]
resultDrag
| handlePos /= newHandlePos = newNode
& L.requests <>~ Seq.fromList [cursorIconReq, RenderOnce]
| otherwise = resultReqs node [cursorIconReq]
resultHover = resultReqs node [cursorIconReq]
_ -> Nothing
where
maxSize = _spsMaxSize state
handlePos = _spsHandlePos state
handleRect = _spsHandleRect state
vp = node ^. L.info . L.viewport
ra = node ^. L.info . L.renderArea
@ -163,12 +221,14 @@ makeSplit isHorizontal config state = widget where
sizeReq2 = sizeReq $ Seq.index children 1
valid1 = sizeReqValid sizeReq1 0 (newSize * oldHandlePos)
valid2 = sizeReqValid sizeReq2 0 (newSize * (1 - oldHandlePos))
handleDragged = _spsHandleDragged state
validSize = valid1 && valid2
handlePosSet = _spsHandlePosSet state
customPos = isJust (_spcHandlePos config)
ignoreSizeReq = Just True == _spcIgnoreChildResize config
sizeReqEquals = (sizeReq1, sizeReq2) == _spsPrevReqs state
useOldPos = customPos || ignoreSizeReq || sizeReqEquals
handlePos
| ignoreSizeReq && handleDragged && valid1 && valid2 = oldHandlePos
| sizeReqEquals && handleDragged && valid1 && valid2 = oldHandlePos
| useOldPos && handlePosSet && validSize = oldHandlePos
| otherwise = calcHandlePos areas newSize
(w1, h1)
| isHorizontal = ((newSize - handleW) * handlePos, rh)
@ -189,11 +249,16 @@ makeSplit isHorizontal config state = widget where
_spsMaxSize = newSize,
_spsPrevReqs = (sizeReq1, sizeReq2)
}
newNode = node
& L.widget .~ makeSplit isHorizontal config newState
events = fmap ($ handlePos) (_spcOnChange config)
reqOnChange = _spcOnChangeReq config
requestPos = setModelPos config handlePos
result = resultWidget node
& L.node . L.widget .~ makeSplit isHorizontal config newState
& L.events .~ Seq.fromList events
& L.requests .~ Seq.fromList (requestPos ++ reqOnChange)
newRas = Seq.fromList [rect1, rect2]
assignedArea = Seq.zip newRas newRas
resized = (resultWidget newNode, assignedArea)
resized = (result, assignedArea)
getValidHandlePos maxDim rect point children = addPoint origin newPoint where
Rect rx ry rw rh = rect
@ -207,8 +272,8 @@ makeSplit isHorizontal config state = widget where
minSize2 = sizeReqMin (sizeReq child2)
maxSize2 = sizeReqMax (sizeReq child2)
(tw, th)
| isHorizontal = (max minSize1 (min maxSize1 $ vx - rx), 0)
| otherwise = (0, max minSize1 (min maxSize1 $ vy - ry))
| isHorizontal = (max minSize1 (min maxSize1 $ abs (vx - rx)), 0)
| otherwise = (0, max minSize1 (min maxSize1 $ abs (vy - ry)))
newPoint
| isHorizontal && tw + minSize2 > maxDim = Point (maxDim - minSize2) th
| isHorizontal && maxDim - tw > maxSize2 = Point (maxDim - maxSize2) th
@ -227,3 +292,16 @@ makeSplit isHorizontal config state = widget where
sizeReq
| isHorizontal = (^. L.info . L.sizeReqW)
| otherwise = (^. L.info . L.sizeReqH)
setModelPos :: SplitCfg s e -> Double -> [WidgetRequest s]
setModelPos cfg
| isJust (_spcHandlePos cfg) = widgetDataSet (fromJust $ _spcHandlePos cfg)
| otherwise = const []
getModelPos :: WidgetEnv s e -> SplitCfg s e -> Maybe Double
getModelPos wenv cfg
| isJust handlePosL = Just $ widgetDataGet model (fromJust handlePosL)
| otherwise = Nothing
where
model = wenv ^. L.model
handlePosL = _spcHandlePos cfg

View File

@ -28,14 +28,9 @@ sizeReqBound sizeReq offset value = max minSize (min maxSize value) where
maxSize = offset + sizeReqMax sizeReq
sizeReqValid :: SizeReq -> Double -> Double -> Bool
sizeReqValid sizeReq offset value = validMin && validMax where
sizeReqValid sizeReq offset value = doubleInRange minSize maxSize value where
minSize = offset + sizeReqMin sizeReq
maxSize = offset + sizeReqMax sizeReq
minDiff = value - minSize
maxDiff = maxSize - value
-- Some calculations may leave small differences in otherwise valid results
validMin = minDiff >= 0 || abs minDiff < 0.0001
validMax = maxDiff >= 0 || abs maxDiff < 0.0001
sizeReqAddStyle :: StyleState -> (SizeReq, SizeReq) -> (SizeReq, SizeReq)
sizeReqAddStyle style (reqW, reqH) = (newReqW, newReqH) where