monomer-1.4.0.0: src/Monomer/Widgets/Containers/Draggable.hs
{-|
Module : Monomer.Widgets.Containers.Draggable
Copyright : (c) 2018 Francisco Vallarino
License : BSD-3-Clause (see the LICENSE file)
Maintainer : fjvallarino@gmail.com
Stability : experimental
Portability : non-portable
Draggable container for a single item. Useful for adding drag support without
having to implement a custom widget. Usually works in tandem with
"Monomer.Widgets.Containers.DropTarget".
Requires a value to identify the content (used when the item is dropped) and the
content to display.
@
dragItem = draggable "item" $ label "This label is draggable"
@
See Tutorial 6 (Composite) for a usage example.
-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE Strict #-}
module Monomer.Widgets.Containers.Draggable (
-- * Configuration
DraggableRender,
DraggableCfg,
draggableMaxDim,
draggableStyle,
draggableRender,
-- * Constructors
draggable,
draggable_
) where
import Control.Applicative ((<|>))
import Control.Lens ((&), (^.), (^?!), (.~), _Just, _1, _2, at, ix)
import Control.Monad (when)
import Data.Default
import Data.Maybe
import qualified Data.Sequence as Seq
import Monomer.Widgets.Container
import qualified Monomer.Lens as L
-- | Rendering function for the dragged state.
type DraggableRender s e
= DraggableCfg s e -- ^ The configuration of the draggable.
-> WidgetEnv s e -- ^ The widget environment.
-> WidgetNode s e -- ^ The widget node.
-> Renderer -- ^ The renderer.
-> IO () -- ^ The drawing actions.
{-|
Configuration options for draggable:
- 'transparency': the alpha level to apply when rendering content in drag mode.
- 'draggableMaxDim': the maximum size of the largest axis when dragging. Keeps
proportions.
- 'draggableStyle': the style to use when the item is being dragged.
- 'draggableRender': rendering function for the dragged state. Allows
customizing this step without implementing a custom widget all the lifecycle
steps.
The regular styling of this component apply only when the item is not being
dragged. To style the dragged container, use draggableStyle.
The transparency config only applies to the inner content.
-}
data DraggableCfg s e = DraggableCfg {
_dgcTransparency :: Maybe Double,
_dgcMaxDim :: Maybe Double,
_dgcDragStyle :: Maybe StyleState,
_dgcCustomRender :: Maybe (DraggableRender s e)
}
instance Default (DraggableCfg s e) where
def = DraggableCfg {
_dgcTransparency = Nothing,
_dgcMaxDim = Nothing,
_dgcDragStyle = Nothing,
_dgcCustomRender = Nothing
}
instance Semigroup (DraggableCfg s e) where
(<>) t1 t2 = DraggableCfg {
_dgcTransparency = _dgcTransparency t2 <|> _dgcTransparency t1,
_dgcMaxDim = _dgcMaxDim t2 <|> _dgcMaxDim t1,
_dgcDragStyle = _dgcDragStyle t2 <|> _dgcDragStyle t1,
_dgcCustomRender = _dgcCustomRender t2 <|> _dgcCustomRender t1
}
instance Monoid (DraggableCfg s e) where
mempty = def
instance CmbTransparency (DraggableCfg s e) where
transparency transp = def {
_dgcTransparency = Just transp
}
{-|
Maximum dimension. Useful when aspect ratio needs to be maintained while at the
same time restricting growth.
-}
draggableMaxDim :: Double -> DraggableCfg s e
draggableMaxDim dim = def {
_dgcMaxDim = Just dim
}
-- | The style of the dragged container.
draggableStyle :: [StyleState] -> DraggableCfg s e
draggableStyle styles = def {
_dgcDragStyle = Just (mconcat styles)
}
-- | Rendering function for the dragged state.
draggableRender :: DraggableRender s e -> DraggableCfg s e
draggableRender render = def {
_dgcCustomRender = Just render
}
-- | Creates a draggable container with a single node as child.
draggable :: DragMsg a => a -> WidgetNode s e -> WidgetNode s e
draggable msg managed = draggable_ msg def managed
-- | Creates a draggable container with a single node as child. Accepts config.
draggable_
:: DragMsg a
=> a
-> [DraggableCfg s e]
-> WidgetNode s e
-> WidgetNode s e
draggable_ msg configs managed = makeNode widget managed where
config = mconcat configs
widget = makeDraggable msg config
makeNode :: Widget s e -> WidgetNode s e -> WidgetNode s e
makeNode widget managedWidget = defaultWidgetNode "draggable" widget
& L.info . L.focusable .~ False
& L.children .~ Seq.singleton managedWidget
makeDraggable :: DragMsg a => a -> DraggableCfg s e -> Widget s e
makeDraggable msg config = widget where
widget = createContainer () def {
containerHandleEvent = handleEvent,
containerGetSizeReq = getSizeReq,
containerResize = resize,
containerRender = render
}
handleEvent wenv node target evt = case evt of
ButtonAction p btn BtnPressed 1 -> Just result where
result = resultReqs node [StartDrag wid path dragMsg]
ButtonAction p btn BtnReleased _ -> Just result where
result = resultReqs node [StopDrag wid]
_ -> Nothing
where
wid = node ^. L.info . L.widgetId
path = node ^. L.info . L.path
dragMsg = WidgetDragMsg msg
getSizeReq :: ContainerGetSizeReqHandler s e
getSizeReq wenv node children = (newReqW, newReqH) where
child = Seq.index children 0
newReqW = child ^. L.info . L.sizeReqW
newReqH = child ^. L.info . L.sizeReqH
resize :: ContainerResizeHandler s e
resize wenv node viewport children = resized where
style = currentStyle wenv node
contentArea = fromMaybe def (removeOuterBounds style viewport)
resized = (resultNode node, Seq.singleton contentArea)
defaultRender config wenv node renderer =
drawStyledAction renderer (moveRect scOffset draggedRect) style $ \_ -> do
saveContext renderer
setTranslation renderer (addPoint scOffset offset)
setScale renderer (Point scale scale)
setGlobalAlpha renderer transparency
widgetRender (cnode ^. L.widget) wenv cnode renderer
restoreContext renderer
where
style = fromMaybe def (_dgcDragStyle config)
transparency = fromMaybe 1 (_dgcTransparency config)
cnode = Seq.index (_wnChildren node) 0
Rect cx cy cw ch = cnode ^. L.info . L.viewport
Point mx my = wenv ^. L.inputStatus . L.mousePos
Point px py = wenv ^?! L.mainBtnPress . _Just . _2
dim = fromMaybe (max cw ch) (_dgcMaxDim config)
scale = dim / max cw ch
offset = Point (mx - px * scale) (my - py * scale)
-- Background rectangle (using draggable style)
(dx, dy) = (cx - px, cy - py)
rect = Rect (mx + dx * scale) (my + dy * scale) (cw * scale) (ch * scale)
draggedRect = fromMaybe rect (addOuterBounds style rect)
scOffset = wenv ^. L.offset
render wenv node renderer = do
when dragged $
createOverlay renderer $ do
renderAction config wenv node renderer
where
dragged = isNodeDragged wenv node
renderAction = fromMaybe defaultRender (_dgcCustomRender config)