tinytools-0.1.0.4: src/Potato/Flow/Controller/Manipulator/Select.hs
{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS_GHC -fno-warn-unused-record-wildcards #-}
module Potato.Flow.Controller.Manipulator.Select (
SelectHandler(..)
) where
import Relude
import Potato.Flow.BroadPhase
import Potato.Flow.Controller.Handler
import Potato.Flow.Controller.Input
import Potato.Flow.Controller.Manipulator.Box
import Potato.Flow.Controller.OwlLayers
import Potato.Flow.Methods.LineDrawer
import Potato.Flow.Controller.Types
import Potato.Flow.Math
import Potato.Flow.Owl
import Potato.Flow.OwlItem
import Potato.Flow.OwlState
import Potato.Flow.RenderCache
import Potato.Flow.SElts
import Control.Exception (assert)
import Data.Default
import Data.Foldable (maximumBy)
import qualified Data.Sequence as Seq
selectBoxFromRelMouseDrag :: RelMouseDrag -> LBox
selectBoxFromRelMouseDrag (RelMouseDrag MouseDrag {..}) = r where
LBox pos' sz' = make_lBox_from_XYs _mouseDrag_to _mouseDrag_from
-- always expand selection by 1
r = LBox pos' (sz' + V2 1 1)
doesOwlSubItemIntersectBox :: OwlTree -> RenderCache -> LBox -> SuperOwl -> Bool
doesOwlSubItemIntersectBox ot rcache lbox sowl = case superOwl_owlSubItem sowl of
OwlSubItemBox x -> does_lBox_intersect_include_zero_area lbox (_sBox_box x)
OwlSubItemTextArea x -> does_lBox_intersect_include_zero_area lbox (_sTextArea_box x)
OwlSubItemLine sline@SAutoLine {..} -> r where
anchors = case renderCache_lookup rcache (_superOwl_id sowl) of
Nothing -> sSimpleLineNewRenderFnComputeCache ot sline
Just (OwlItemCache_Line lar _) -> lar
_ -> assert False (sSimpleLineNewRenderFnComputeCache ot sline)
r = lineAnchorsForRender_doesIntersectBox anchors lbox
_ -> False
-- TODO ignore locked and hidden elements here
-- for now hidden + locked elements ARE inctluded in BroadPhaseState
selectMagic :: OwlPFState -> RenderCache -> LayerMetaMap -> BroadPhaseState -> RelMouseDrag -> Selection
selectMagic pfs rcache lmm bps rmd = r where
selectBox = selectBoxFromRelMouseDrag rmd
boxSize = lBox_area selectBox
singleClick = boxSize == 1
isboxshaped sowl = case _superOwl_elt sowl of
OwlItem _ (OwlSubItemBox _) -> True
OwlItem _ (OwlSubItemTextArea _) -> True
_ -> False
unculledrids = broadPhase_cull_includeZero selectBox (_broadPhaseState_bPTree bps)
unculledsowls = fmap (\rid -> owlTree_mustFindSuperOwl (_owlPFState_owlTree pfs) rid) unculledrids
selectedsowls'' = flip filter unculledsowls $ \case
-- if it's box shaped, there's no need to test for intersection as we already know it intersects based on broadphase
sowl | isboxshaped sowl -> True
sowl -> doesOwlSubItemIntersectBox (_owlPFState_owlTree pfs) rcache selectBox sowl
-- remove lock and hidden stuff
selectedsowls' = flip filter selectedsowls'' $ \sowl -> not (layerMetaMap_isInheritHiddenOrLocked (_owlPFState_owlTree pfs) (_superOwl_id sowl) lmm)
-- TODO consider using makeSortedSuperOwlParliament instead (prob a little faster?)
selectedsowls = if singleClick
-- single click, select top elt only
then case selectedsowls' of
[] -> []
_ -> [maximumBy (\s1 s2 -> owlTree_superOwl_comparePosition (_owlPFState_owlTree pfs) s2 s1) selectedsowls']
-- otherwise select everything
else selectedsowls'
r = makeSortedSuperOwlParliament (_owlPFState_owlTree pfs) $ Seq.fromList selectedsowls
data SelectHandler = SelectHandler {
_selectHandler_selectArea :: LBox
}
instance Default SelectHandler where
def = SelectHandler {
_selectHandler_selectArea = LBox 0 0
}
instance PotatoHandler SelectHandler where
pHandlerName _ = handlerName_select
pHandleMouse sh phi@PotatoHandlerInput {..} rmd@(RelMouseDrag MouseDrag {..}) = Just $ case _mouseDrag_state of
MouseDragState_Down -> r where
nextSelection@(SuperOwlParliament sowls) = selectMagic _potatoHandlerInput_pFState _potatoHandlerInput_renderCache (_layersState_meta _potatoHandlerInput_layersState) _potatoHandlerInput_broadPhase rmd
-- since selection came from canvas, it's definitely a valid CanvasSelection, no need to convert
nextCanvasSelection = CanvasSelection sowls
shiftClick = isJust $ find (==KeyModifier_Shift) _mouseDrag_modifiers
r = if isParliament_null nextSelection || shiftClick
then captureWithNoChange sh
-- special select+drag case, override the selection
-- NOTE BoxHandler here is used to move all SElt types, upon release, it will either return the correct handler type or not capture the input in which case Goat will set the correct handler type
else case pHandleMouse (def { _boxHandler_creation = BoxCreationType_DragSelect }) (phi { _potatoHandlerInput_selection = nextSelection, _potatoHandlerInput_canvasSelection = nextCanvasSelection }) rmd of
-- force the selection from outside the handler and ignore the new selection results returned by pho (which should always be Nothing)
Just pho -> assert (not . handlerOutputAction_isSelect $ _potatoHandlerOutput_action pho)
$ pho { _potatoHandlerOutput_action = HOA_Select False nextSelection }
Nothing -> error "handler was expected to capture this mouse state"
MouseDragState_Dragging -> setHandlerOnly sh {
_selectHandler_selectArea = selectBoxFromRelMouseDrag rmd
}
MouseDragState_Up -> def { _potatoHandlerOutput_action = HOA_Select shiftClick newSelection } where
shiftClick = isJust $ find (==KeyModifier_Shift) (_mouseDrag_modifiers)
newSelection = selectMagic _potatoHandlerInput_pFState _potatoHandlerInput_renderCache (_layersState_meta _potatoHandlerInput_layersState) _potatoHandlerInput_broadPhase rmd
MouseDragState_Cancelled -> def
pHandleKeyboard _ PotatoHandlerInput {..} _ = Nothing
pRenderHandler sh PotatoHandlerInput {..} = HandlerRenderOutput (fmap defaultRenderHandle $ substract_lBox full inside) where
full@(LBox (V2 x y) (V2 w h)) = _selectHandler_selectArea sh
inside = if w > 2 && h > 2
then LBox (V2 (x+1) (y+1)) (V2 (w-2) (h-2))
else LBox 0 0
pIsHandlerActive _ = HAS_Active_Mouse
pHandlerTool _ = Just Tool_Select