packages feed

tinytools-0.1.0.0: src/Potato/Flow/Controller/Goat.hs

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RecursiveDo     #-}

module Potato.Flow.Controller.Goat (
  GoatFocusedArea(..)
  , goatState_hasUnsavedChanges
  , makeGoatState
  , goatState_pFState
  , goatState_selectedTool
  , GoatState(..)
  , GoatCmd(..)
  , foldGoatFn

  -- exposed for testing
  , potatoHandlerInputFromGoatState
) where

import           Relude

import           Potato.Data.Text.Unicode
import           Potato.Flow.BroadPhase
import           Potato.Flow.Configuration
import           Potato.Flow.Controller.Handler
import           Potato.Flow.Controller.Input
import           Potato.Flow.Controller.Manipulator.Box
import           Potato.Flow.Controller.Manipulator.CartLine
import           Potato.Flow.Controller.Manipulator.Common
import           Potato.Flow.Controller.Manipulator.Layers
import           Potato.Flow.Controller.Manipulator.Line
import           Potato.Flow.Controller.Manipulator.Pan
import           Potato.Flow.Controller.Manipulator.Select
import           Potato.Flow.Controller.OwlLayers
import           Potato.Flow.Controller.Types
import           Potato.Flow.Llama
import           Potato.Flow.Math
import           Potato.Flow.Owl
import           Potato.Flow.OwlItem
import           Potato.Flow.OwlState
import           Potato.Flow.OwlWorkspace
import           Potato.Flow.Render
import           Potato.Flow.SEltMethods
import           Potato.Flow.Types

import           Control.Exception                           (assert)
import           Data.Default
import qualified Data.IntMap                                 as IM
import qualified Data.IntSet                                 as IS
import           Data.Maybe
import qualified Data.Sequence                               as Seq
import qualified Data.Text                                   as T


catMaybesSeq :: Seq (Maybe a) -> Seq a
catMaybesSeq = fmap fromJust . Seq.filter isJust

data GoatFocusedArea =
  GoatFocusedArea_Layers
  | GoatFocusedArea_Canvas
  | GoatFocusedArea_Other -- focus is some area that is not owned by tinytools (e.g. the params widgets)
  | GoatFocusedArea_None
  deriving (Eq, Show)

-- TODO move into its own file
data GoatState = GoatState {

    -- TODO make GoatTab
    -- unique to each document
    _goatState_workspace                 :: OwlPFWorkspace
    , _goatState_pan                     :: XY -- panPos is position of upper left corner of canvas relative to screen
    , _goatState_selection               :: Selection
    , _goatState_canvasSelection         :: CanvasSelection
    , _goatState_broadPhaseState         :: BroadPhaseState
    , _goatState_layersState             :: LayersState
    , _goatState_renderedCanvas          :: RenderedCanvasRegion
    , _goatState_renderedSelection       :: RenderedCanvasRegion -- TODO need sparse variant
    , _goatState_handler                 :: SomePotatoHandler
    , _goatState_layersHandler           :: SomePotatoHandler
    -- TODO consider moving into _goatState_workspace
    , _goatState_attachmentMap           :: AttachmentMap -- map of targets to things attached to it. This is a cache that gets updated over time and can be regenerated from the current OwlTree
    , _goatState_renderCache             :: RenderCache

    -- shared across documents
    , _goatState_configuration           :: PotatoConfiguration -- maybe also move PotatoDefaultParameters into this
    , _goatState_potatoDefaultParameters :: PotatoDefaultParameters
    , _goatState_mouseDrag               :: MouseDrag -- last mouse dragging state, this is a little questionable, arguably we should only store stuff needed, not the entire mouseDrag
    , _goatState_screenRegion            :: XY -- the screen dimensions
    , _goatState_clipboard               :: Maybe SEltTree
    , _goatState_focusedArea             :: GoatFocusedArea
    , _goatState_unbrokenInput       :: Text -- grapheme clusters are inputed as several keyboard character events so we track these inputs here

    -- debug stuff (shared across documents)
    , _goatState_debugLabel              :: Text
    , _goatState_debugCommands           :: [GoatCmd]

  } deriving (Show)

makeGoatState :: XY -> (OwlPFState, ControllerMeta) -> GoatState
makeGoatState (V2 screenx screeny) (initialstate, controllermeta) = goat where
    initialowlpfstate = initialstate
    -- initialize broadphase with initial state
    initialAsSuperOwlChanges = IM.mapWithKey (\rid (oem, oe) -> Just $ SuperOwl rid oem oe) . _owlTree_mapping . _owlPFState_owlTree $ initialstate
    (_, initialbp) = update_bPTree initialowlpfstate initialAsSuperOwlChanges emptyBPTree
    initiallayersstate = makeLayersStateFromOwlPFState initialowlpfstate (_controllerMeta_layers controllermeta)

    -- TODO DELETE
    -- TODO wrap this in a helper function in Render
    -- TODO we want to render the whole screen, not just the canvas
    initialCanvasBox = _sCanvas_box . _owlPFState_canvas $ initialowlpfstate
    initialselts = fmap (\(_, oelt) -> _owlItem_subItem oelt) . toList . _owlTree_mapping . _owlPFState_owlTree $ initialowlpfstate
    initialemptyrcr = emptyRenderedCanvasRegion initialCanvasBox
    initialrendercontext = RenderContext {
      _renderContext_owlTree = hasOwlTree_owlTree initialowlpfstate
      , _renderContext_layerMetaMap = _layersState_meta initiallayersstate
      , _renderContext_broadPhase = initialbp -- this is ignored but we may as well set in correctly
      , _renderContext_renderedCanvasRegion = initialemptyrcr
    }
    initialrc = _renderContext_renderedCanvasRegion $ render initialCanvasBox initialselts initialrendercontext

    goat = GoatState {
        _goatState_workspace      = loadOwlPFStateIntoWorkspace (initialstate) emptyWorkspace
        , _goatState_pan             = _controllerMeta_pan controllermeta
        , _goatState_mouseDrag       = def
        , _goatState_handler         = SomePotatoHandler EmptyHandler
        , _goatState_layersHandler   = SomePotatoHandler (def :: LayersHandler)
        , _goatState_configuration = def
        , _goatState_potatoDefaultParameters = def
        , _goatState_attachmentMap = owlTree_makeAttachmentMap (_owlPFState_owlTree initialstate)
        , _goatState_debugLabel      = ""
        , _goatState_selection       = isParliament_empty
        , _goatState_canvasSelection = CanvasSelection Seq.empty
        , _goatState_broadPhaseState = initialbp
        , _goatState_renderedCanvas = initialrc
        , _goatState_renderedSelection = initialemptyrcr
        , _goatState_layersState     = initiallayersstate
        , _goatState_renderCache = emptyRenderCache
        , _goatState_clipboard = Nothing
        , _goatState_focusedArea = GoatFocusedArea_None
        , _goatState_unbrokenInput = ""
        , _goatState_screenRegion = V2 screenx screeny - (_controllerMeta_pan controllermeta)
        , _goatState_debugCommands = []
      }


goatState_pFState :: GoatState -> OwlPFState
goatState_pFState = _owlPFWorkspace_owlPFState . _goatState_workspace

-- TODO instance GoatState HasOwlTree
goatState_owlTree :: GoatState -> OwlTree
goatState_owlTree = _owlPFState_owlTree . goatState_pFState

goatState_hasUnsavedChanges :: GoatState -> Bool
goatState_hasUnsavedChanges = llamaStack_hasUnsavedChanges . _owlPFWorkspace_llamaStack . _goatState_workspace

goatState_selectedTool :: GoatState -> Tool
goatState_selectedTool = fromMaybe Tool_Select . pHandlerTool . _goatState_handler

-- TODO deprecate this in favor of Endo style
data GoatCmd =
  GoatCmdTool Tool
  | GoatCmdSetFocusedArea GoatFocusedArea
  | GoatCmdLoad EverythingLoadState

  -- command based input for widgets not owned by tiny tools
  | GoatCmdWSEvent WSEvent
  | GoatCmdSetCanvasRegionDim XY
  | GoatCmdNewFolder Text

  -- direct input for widgets owned by tiny tools
  | GoatCmdMouse LMouseData
  | GoatCmdKeyboard KeyboardData

  -- debug nonsense
  | GoatCmdSetDebugLabel Text
  deriving (Show)



-- Ok, don't think this needs to be a part of GoatCmdTempOutput but does need to be a part of GoatState
-- TODO do this later
{-
data DynGoatFlags = DynGoatFlags {
  _dynGoatFlags_tool           = r_tool
  , _dynGoatFlags_selection    = r_selection
  , _dynGoatFlags_layers       = r_layers
  , _dynGoatFlags_pan          = r_pan
  , _dynGoatFlags_broadPhase   = r_broadphase
  , _dynGoatFlags_canvas = r_canvas
  , _dynGoatFlags_renderedCanvas = r_renderedCanvas
  , _dynGoatFlags_handlerRenderOutput =  r_handlerRenderOutput
} deriving (Show)

data GoatStateFlag = GoatStateFlag_Tool | GoatStateFlag_Selection | GoatStateFlag_Layers | GoatStateFlag_Pan | GoatStateFlag_BroadPhase | GoatStateFlag_Canvas | GoatStateFlag_RenderedCanvasRegion | GoatStateFlag_HandlerRenderOutput deriving (Show, Eq)
-}


data GoatCmdTempOutput = GoatCmdTempOutput {
  _goatCmdTempOutput_goatState               :: GoatState
  --, _goatCmdTempOutput_wasCanvasInput :: Bool
  --, _goatCmdTempOutput_wasLayerInput :: Bool

  , _goatCmdTempOutput_nextHandler           :: Maybe SomePotatoHandler

  , _goatCmdTempOutput_select                :: Maybe (Bool, Selection)
  , _goatCmdTempOutput_pFEvent               :: Maybe (Bool, WSEvent) -- bool is true if it was a canvas handler event
  , _goatCmdTempOutput_pan                   :: Maybe XY
  , _goatCmdTempOutput_layersState           :: Maybe LayersState
  , _goatCmdTempOutput_changesFromToggleHide :: SuperOwlChanges
} deriving (Show)

-- helpers to extract stuff out of goatState because we use record wildcards and can't access otherwise
goatCmdTempOutput_screenRegion :: GoatCmdTempOutput -> XY
goatCmdTempOutput_screenRegion = _goatState_screenRegion . _goatCmdTempOutput_goatState

goatCmdTempOutput_layersHandler :: GoatCmdTempOutput -> SomePotatoHandler
goatCmdTempOutput_layersHandler = _goatState_layersHandler . _goatCmdTempOutput_goatState


instance Default GoatCmdTempOutput where
  def = GoatCmdTempOutput {

      -- TODO just don't use Default if you're gonna do this...
      _goatCmdTempOutput_goatState = undefined --error "this is expected to be overwritten during initialization"

      , _goatCmdTempOutput_nextHandler  = Nothing
      , _goatCmdTempOutput_select      = Nothing
      , _goatCmdTempOutput_pFEvent     = Nothing
      , _goatCmdTempOutput_pan         = Nothing
      , _goatCmdTempOutput_layersState = Nothing
      , _goatCmdTempOutput_changesFromToggleHide = IM.empty
    }

makeGoatCmdTempOutputFromNothing :: GoatState -> GoatCmdTempOutput
makeGoatCmdTempOutputFromNothing goatState = def {
    _goatCmdTempOutput_goatState = goatState

    -- NOTE the value of _potatoHandlerOutput_nextHandler is not directly translated here
    -- PotatoHandlerOutput interpretation: isNothing _potatoHandlerOutput_nextHandler => handler does not capture input
    -- GoatCmdTempOutput interpretation (when non-canvas input):
    --    -isNothing _potatoHandlerOutput_nextHandler => the particular event we just processed is not related to the canvas handler
    --    -so in this case we default _goatCmdTempOutput_nextHandler = Just _goatState_handler
    , _goatCmdTempOutput_nextHandler = Just (_goatState_handler goatState)
  }

makeGoatCmdTempOutputFromNothingClearHandler :: GoatState -> GoatCmdTempOutput
makeGoatCmdTempOutputFromNothingClearHandler goatState = def {
    _goatCmdTempOutput_goatState = goatState
  }

makeGoatCmdTempOutputFromEvent :: GoatState -> WSEvent -> GoatCmdTempOutput
makeGoatCmdTempOutputFromEvent goatState wsev = (makeGoatCmdTempOutputFromNothing goatState) {
    _goatCmdTempOutput_pFEvent = Just (False, wsev)
  }

makeGoatCmdTempOutputFromMaybeEvent :: GoatState -> Maybe WSEvent -> GoatCmdTempOutput
makeGoatCmdTempOutputFromMaybeEvent goatState mwsev = (makeGoatCmdTempOutputFromNothing goatState) {
    _goatCmdTempOutput_pFEvent = fmap (\x -> (False,x)) mwsev
  }

makeGoatCmdTempOutputFromPotatoHandlerOutput :: GoatState -> PotatoHandlerOutput -> GoatCmdTempOutput
makeGoatCmdTempOutputFromPotatoHandlerOutput goatState PotatoHandlerOutput {..} =  def {
    _goatCmdTempOutput_goatState = goatState
    , _goatCmdTempOutput_nextHandler = _potatoHandlerOutput_nextHandler
    , _goatCmdTempOutput_select      = _potatoHandlerOutput_select
    , _goatCmdTempOutput_pFEvent     = fmap (\x -> (True,x)) _potatoHandlerOutput_pFEvent
    , _goatCmdTempOutput_pan         = _potatoHandlerOutput_pan
    , _goatCmdTempOutput_layersState = _potatoHandlerOutput_layersState
    , _goatCmdTempOutput_changesFromToggleHide = _potatoHandlerOutput_changesFromToggleHide -- actually not needed, only used by layers
  }


makeGoatCmdTempOutputFromLayersPotatoHandlerOutput :: GoatState -> PotatoHandlerOutput -> GoatCmdTempOutput
makeGoatCmdTempOutputFromLayersPotatoHandlerOutput goatState PotatoHandlerOutput {..} =  def {
    _goatCmdTempOutput_goatState = goatState {
        _goatState_layersHandler = case _potatoHandlerOutput_nextHandler of
          Just h  -> h
          Nothing -> error "expected LayersHandler to return a new handler"
      }
    -- TODO flag that this was not canvas input
    , _goatCmdTempOutput_nextHandler = Nothing
    , _goatCmdTempOutput_select      = _potatoHandlerOutput_select
    , _goatCmdTempOutput_pFEvent     = fmap (\x -> (False,x)) _potatoHandlerOutput_pFEvent
    , _goatCmdTempOutput_pan         = _potatoHandlerOutput_pan
    , _goatCmdTempOutput_layersState = _potatoHandlerOutput_layersState
    , _goatCmdTempOutput_changesFromToggleHide = _potatoHandlerOutput_changesFromToggleHide
  }

makeGoatCmdTempOutputFromUpdateGoatStateFocusedArea :: GoatState -> GoatFocusedArea -> GoatCmdTempOutput
makeGoatCmdTempOutputFromUpdateGoatStateFocusedArea goatState gfa = r where
  didchange = gfa /= _goatState_focusedArea goatState
  goatstatewithnewfocus = goatState { _goatState_focusedArea = gfa }
  noactionneeded = makeGoatCmdTempOutputFromNothing goatstatewithnewfocus
  potatoHandlerInput = potatoHandlerInputFromGoatState goatState
  -- if we were renaming, finalize the rename operation by sending a fake return key event, I can't think of a less ad-hoc way to do this
  r = if didchange && pHandlerName (_goatState_layersHandler goatState) == handlerName_layersRename
    then assert (_goatState_focusedArea goatState == GoatFocusedArea_Layers) $ case pHandleKeyboard (_goatState_layersHandler goatState) potatoHandlerInput (KeyboardData KeyboardKey_Return []) of
      Nothing -> noactionneeded
      Just pho -> makeGoatCmdTempOutputFromLayersPotatoHandlerOutput goatstatewithnewfocus pho
    else noactionneeded

-- | hack function for resetting both handlers
-- It would be nice if we actually cancel/reset the handlers (such that in progress operations are undone), but I don't think it really matters
forceResetBothHandlersAndMakeGoatCmdTempOutput :: GoatState -> GoatCmdTempOutput
forceResetBothHandlersAndMakeGoatCmdTempOutput goatState = r where

  -- I think this is Ok
  msph_h = Nothing
  msph_lh = Just (SomePotatoHandler (def :: LayersHandler))

  r = def {
      _goatCmdTempOutput_goatState = goatState {
          _goatState_layersHandler = case msph_lh of
            Just x  -> x
            Nothing -> error "expected LayersHandler to return a new handler"
        }
      , _goatCmdTempOutput_nextHandler = msph_h
    }

makeHandlerFromNewTool :: GoatState -> Tool -> SomePotatoHandler
makeHandlerFromNewTool GoatState{..} = \case
  Tool_Box    -> SomePotatoHandler $ def { _boxHandler_creation = BoxCreationType_Box }
  Tool_Line   -> SomePotatoHandler $ def { _autoLineHandler_isCreation = True }
  Tool_CartLine -> SomePotatoHandler $ def { _cartLineHandler_isCreation = True }
  Tool_Select -> makeHandlerFromSelection _goatState_canvasSelection
  Tool_Text   -> SomePotatoHandler $ def { _boxHandler_creation = BoxCreationType_Text }
  Tool_TextArea -> SomePotatoHandler $ def { _boxHandler_creation = BoxCreationType_TextArea }
  Tool_Pan           -> SomePotatoHandler $ (def :: PanHandler)


-- TODO rename to makeHandlerFromCanvasSelection
makeHandlerFromSelection :: CanvasSelection -> SomePotatoHandler
makeHandlerFromSelection selection = case computeSelectionType selection of
  SMTBox         -> SomePotatoHandler $ (def :: BoxHandler)
  SMTBoxText     -> SomePotatoHandler $ (def :: BoxHandler)
  SMTLine        -> SomePotatoHandler $ (def :: AutoLineHandler)
  SMTTextArea    -> SomePotatoHandler $ (def :: BoxHandler)
  SMTBoundingBox -> SomePotatoHandler $ (def :: BoxHandler)
  SMTNone        -> SomePotatoHandler EmptyHandler

maybeUpdateHandlerFromSelection :: SomePotatoHandler -> CanvasSelection -> SomePotatoHandler
maybeUpdateHandlerFromSelection sph selection = case sph of
  SomePotatoHandler h -> if pIsHandlerActive h
    then sph
    else makeHandlerFromSelection selection

makeClipboard :: GoatState -> Maybe SEltTree
makeClipboard goatState@GoatState {..} = r where
  r = if isParliament_null _goatState_selection
    then _goatState_clipboard
    else Just $ superOwlParliament_toSEltTree (goatState_owlTree goatState) _goatState_selection

deleteSelectionEvent :: GoatState -> Maybe WSEvent
deleteSelectionEvent GoatState {..} = if isParliament_null _goatState_selection
  then Nothing
  else Just $ WSERemoveEltAndUpdateAttachments (superOwlParliament_toOwlParliament _goatState_selection) (_goatState_attachmentMap)

potatoHandlerInputFromGoatState :: GoatState -> PotatoHandlerInput
potatoHandlerInputFromGoatState GoatState {..} = r where
  last_workspace = _goatState_workspace
  last_pFState = _owlPFWorkspace_owlPFState last_workspace
  r = PotatoHandlerInput {
    _potatoHandlerInput_pFState       = last_pFState
    , _potatoHandlerInput_potatoDefaultParameters = _goatState_potatoDefaultParameters
    , _potatoHandlerInput_broadPhase  = _goatState_broadPhaseState
    , _potatoHandlerInput_renderCache = _goatState_renderCache

    -- the screen region in canvas space
    , _potatoHandlerInput_screenRegion = LBox (-_goatState_pan) _goatState_screenRegion

    , _potatoHandlerInput_layersState     = _goatState_layersState
    , _potatoHandlerInput_selection   = _goatState_selection
    , _potatoHandlerInput_canvasSelection = _goatState_canvasSelection
  }




-- | filters out keyboard input based on the configuration
-- must provide last character-unbroken sequence of text input in order to detect grapheme cluster
-- relies on assumption 🙈
-- let ...(n-1)(n) be a sequence of codepoints that is a grapheme cluster
-- then ...(n-1) is also a grapheme cluster
potatoModifyKeyboardKey :: PotatoConfiguration -> Text -> KeyboardData -> Maybe KeyboardData
potatoModifyKeyboardKey PotatoConfiguration {..} lastUnbrokenCharacters k = case k of
  KeyboardData (KeyboardKey_Char c) mods -> r where
    fulltext = T.snoc lastUnbrokenCharacters c
    r = if not _potatoConfiguration_allowGraphemeClusters && endsInGraphemeCluster fulltext
      then Nothing
      else case _potatoConfiguration_allowOrReplaceUnicodeWideChars of
        Nothing -> Just k
        Just x -> if getCharWidth c > 1
          then maybe Nothing (\nc -> Just (KeyboardData (KeyboardKey_Char nc) mods))  x
          else Just k
  _ -> Just k

-- TODO probably should have done "Endo GoatState" instead of "GoatCmd"
-- TODO extract this method into another file
-- TODO make State monad for this
foldGoatFn :: GoatCmd -> GoatState -> GoatState
--foldGoatFn cmd goatStateIgnore = trace ("FOLDING " <> show cmd) $ finalGoatState where
foldGoatFn cmd goatStateIgnore = finalGoatState where

  -- TODO do some sort of rolling buffer here prob
  -- NOTE even with a rolling buffer, I think this will leak if no one forces the thunk!
  --goatState = goatStateIgnore { _goatState_debugCommands = cmd:_goatState_debugCommands }
  goatState' = goatStateIgnore

  -- it's convenient/lazy to reset unbrokenInput here, this will get overriden in cases where it needs to be
  goatState = goatState' { _goatState_unbrokenInput = "" }
  last_unbrokenInput = _goatState_unbrokenInput goatState'
  last_workspace = _goatState_workspace goatState
  last_pFState = _owlPFWorkspace_owlPFState last_workspace

  potatoHandlerInput = potatoHandlerInputFromGoatState goatState

  -- TODO this step can update OwlState built-in cache (via select operation)
  -- | Process commands |
  goatCmdTempOutput = case (_goatState_handler goatState) of
    SomePotatoHandler handler -> case cmd of
      GoatCmdSetDebugLabel x -> makeGoatCmdTempOutputFromNothing $ goatState { _goatState_debugLabel = x }
      GoatCmdSetCanvasRegionDim x -> makeGoatCmdTempOutputFromNothing $ goatState { _goatState_screenRegion = x }
      GoatCmdWSEvent x ->  makeGoatCmdTempOutputFromEvent goatState x
      GoatCmdNewFolder x -> makeGoatCmdTempOutputFromEvent goatState newFolderEv where
        folderPos = lastPositionInSelection (_owlPFState_owlTree . _owlPFWorkspace_owlPFState $  (_goatState_workspace goatState)) (_goatState_selection goatState)
        newFolderEv = WSEAddFolder (folderPos, x)
      GoatCmdLoad (spf, cm) -> r where
        -- HACK this won't get generated until later but we need this to generate layersState...
        -- someday we'll split up foldGoatFn by `GoatCmd` (or switch to Endo `GoatState`) and clean this up
        tempOwlPFStateHack = sPotatoFlow_to_owlPFState spf
        r = (makeGoatCmdTempOutputFromEvent goatState (WSELoad spf)) {
            _goatCmdTempOutput_pan = Just $ _controllerMeta_pan cm
            , _goatCmdTempOutput_layersState = Just $ makeLayersStateFromOwlPFState tempOwlPFStateHack (_controllerMeta_layers cm)
           }


      GoatCmdTool x -> r where
        -- TODO do we need to cancel the old handler?
        r = makeGoatCmdTempOutputFromNothing (goatState { _goatState_handler = makeHandlerFromNewTool goatState x })

      GoatCmdSetFocusedArea gfa -> makeGoatCmdTempOutputFromUpdateGoatStateFocusedArea goatState gfa

      GoatCmdMouse mouseData ->
        let
          sameSource = _mouseDrag_isLayerMouse (_goatState_mouseDrag goatState) == _lMouseData_isLayerMouse mouseData
          mouseSourceFailure = _mouseDrag_state (_goatState_mouseDrag goatState) /= MouseDragState_Up && not sameSource
          mouseDrag = case _mouseDrag_state (_goatState_mouseDrag goatState) of
            MouseDragState_Up        -> newDrag mouseData
            MouseDragState_Cancelled -> (continueDrag mouseData (_goatState_mouseDrag goatState)) { _mouseDrag_state = MouseDragState_Cancelled }

            _                        ->  continueDrag mouseData (_goatState_mouseDrag goatState)

          canvasDrag = toRelMouseDrag last_pFState (_goatState_pan goatState) mouseDrag

          goatState_withNewMouse = goatState {
              _goatState_mouseDrag = mouseDrag
              , _goatState_focusedArea = if isLayerMouse then GoatFocusedArea_Layers else GoatFocusedArea_Canvas
            }
          -- TODO call makeGoatCmdTempOutputFromUpdateGoatStateFocusedArea and merge outputs instead UG, or is there a trick for us to be renentrant into foldGoatFn?

          noChangeOutput = makeGoatCmdTempOutputFromNothing goatState_withNewMouse

          isLayerMouse = _mouseDrag_isLayerMouse mouseDrag

        in case _mouseDrag_state mouseDrag of

          -- hack to recover after sameSource issue
          -- TODO TEST
          _ | mouseSourceFailure -> assert False $
            forceResetBothHandlersAndMakeGoatCmdTempOutput goatState_withNewMouse

          -- if mouse was cancelled, update _goatState_mouseDrag accordingly
          MouseDragState_Cancelled -> if _lMouseData_isRelease mouseData
            then makeGoatCmdTempOutputFromNothing $ goatState_withNewMouse { _goatState_mouseDrag = def }
            else noChangeOutput -- still cancelled

          -- if mouse is intended for layers
          _ | isLayerMouse -> case pHandleMouse (_goatState_layersHandler goatState) potatoHandlerInput (RelMouseDrag mouseDrag) of
            Just pho -> makeGoatCmdTempOutputFromLayersPotatoHandlerOutput goatState_withNewMouse pho
            Nothing  -> noChangeOutput

          -- if middle mouse button, create a temporary PanHandler
          MouseDragState_Down | _lMouseData_button mouseData == MouseButton_Middle -> r where
            panhandler = def { _panHandler_maybePrevHandler = Just (SomePotatoHandler handler) }
            r = case pHandleMouse panhandler potatoHandlerInput canvasDrag of
              Just pho -> makeGoatCmdTempOutputFromPotatoHandlerOutput goatState_withNewMouse pho
              Nothing -> error "PanHandler expected to capture mouse input"

          -- pass onto canvas handler
          _ -> case pHandleMouse handler potatoHandlerInput canvasDrag of
            Just pho -> makeGoatCmdTempOutputFromPotatoHandlerOutput goatState_withNewMouse pho

            -- input not captured by handler, pass onto select or select+drag
            Nothing | _mouseDrag_state mouseDrag == MouseDragState_Down -> assert (not $ pIsHandlerActive handler) r where
              r = case pHandleMouse (def :: SelectHandler) potatoHandlerInput canvasDrag of
                Just pho -> makeGoatCmdTempOutputFromPotatoHandlerOutput goatState_withNewMouse pho
                Nothing -> error "handler was expected to capture this mouse state"

            Nothing -> error $ "handler " <> show (pHandlerName handler) <> "was expected to capture mouse state " <> show (_mouseDrag_state mouseDrag)

      GoatCmdKeyboard kbd' -> let
          next_unbrokenInput = case kbd' of
            KeyboardData (KeyboardKey_Char c) _ -> T.snoc last_unbrokenInput c
            _ -> ""
          mkbd =   potatoModifyKeyboardKey (_goatState_configuration goatState) last_unbrokenInput kbd'
          goatState_withKeyboard =  goatState { _goatState_unbrokenInput = next_unbrokenInput}
        in case mkbd of
          Nothing -> makeGoatCmdTempOutputFromNothing goatState_withKeyboard
          -- special case, treat escape cancel mouse drag as a mouse input
          Just (KeyboardData KeyboardKey_Esc _) | mouseDrag_isActive (_goatState_mouseDrag goatState_withKeyboard) -> r where
            canceledMouse = cancelDrag (_goatState_mouseDrag goatState_withKeyboard)
            goatState_withNewMouse = goatState_withKeyboard {
                _goatState_mouseDrag = canceledMouse

                -- escape will cancel mouse focus
                -- TODO this isn't correct, you have some handlers that cancel into each other, you should only reset to GoatFocusedArea_None if they canceled to Nothing
                , _goatState_focusedArea = GoatFocusedArea_None

              }

            -- TODO use _goatState_focusedArea instead
            r = if _mouseDrag_isLayerMouse (_goatState_mouseDrag goatState_withKeyboard)
              then case pHandleMouse (_goatState_layersHandler goatState_withKeyboard) potatoHandlerInput (RelMouseDrag canceledMouse) of
                Just pho -> makeGoatCmdTempOutputFromLayersPotatoHandlerOutput goatState_withNewMouse pho
                Nothing  -> makeGoatCmdTempOutputFromNothingClearHandler goatState_withNewMouse
              else case pHandleMouse handler potatoHandlerInput (toRelMouseDrag last_pFState (_goatState_pan goatState_withKeyboard) canceledMouse) of
                Just pho -> makeGoatCmdTempOutputFromPotatoHandlerOutput goatState_withNewMouse pho
                Nothing  -> makeGoatCmdTempOutputFromNothingClearHandler goatState_withNewMouse

          -- we are in the middle of mouse drag, ignore all keyboard inputs
          -- perhaps a better way to do this is to have handlers capture all inputs when active
          Just _ | mouseDrag_isActive (_goatState_mouseDrag goatState_withKeyboard) -> makeGoatCmdTempOutputFromNothing goatState_withKeyboard

          Just kbd ->
            let
              maybeHandleLayers = do
                guard $ _mouseDrag_isLayerMouse (_goatState_mouseDrag goatState_withKeyboard)
                pho <- pHandleKeyboard (_goatState_layersHandler goatState_withKeyboard) potatoHandlerInput kbd
                return $ makeGoatCmdTempOutputFromLayersPotatoHandlerOutput goatState_withKeyboard pho
            in case maybeHandleLayers of
              Just x -> x
              Nothing -> case pHandleKeyboard handler potatoHandlerInput kbd of
                Just pho -> makeGoatCmdTempOutputFromPotatoHandlerOutput goatState_withKeyboard pho
                -- input not captured by handler
                -- TODO consider wrapping this all up in KeyboardHandler or something? Unfortunately, copy needs to modify goatState_withKeyboard which PotatoHandlerOutput can't atm
                Nothing -> case kbd of
                  KeyboardData KeyboardKey_Esc _ ->
                    (makeGoatCmdTempOutputFromNothing goatState_withKeyboard) {
                        -- TODO change tool back to select?
                        -- cancel selection if we are in a neutral mouse state and there is no handler
                        _goatCmdTempOutput_select = case _mouseDrag_state (_goatState_mouseDrag goatState_withKeyboard) of
                          MouseDragState_Up        -> Just (False, isParliament_empty)
                          MouseDragState_Cancelled -> Just (False, isParliament_empty)
                          _                        -> Nothing
                      }

                  KeyboardData (KeyboardKey_Delete) [] -> r where
                    r = makeGoatCmdTempOutputFromMaybeEvent goatState_withKeyboard (deleteSelectionEvent goatState_withKeyboard)
                  KeyboardData (KeyboardKey_Backspace) [] -> r where
                    r = makeGoatCmdTempOutputFromMaybeEvent goatState_withKeyboard (deleteSelectionEvent goatState_withKeyboard)

                  KeyboardData (KeyboardKey_Char 'c') [KeyModifier_Ctrl] -> r where
                    copied = makeClipboard goatState_withKeyboard
                    r = makeGoatCmdTempOutputFromNothing $ goatState_withKeyboard { _goatState_clipboard = copied }
                  KeyboardData (KeyboardKey_Char 'x') [KeyModifier_Ctrl] -> r where
                    copied = makeClipboard goatState_withKeyboard
                    r = makeGoatCmdTempOutputFromMaybeEvent (goatState_withKeyboard { _goatState_clipboard = copied }) (deleteSelectionEvent goatState_withKeyboard)
                  KeyboardData (KeyboardKey_Char 'v') [KeyModifier_Ctrl] -> case _goatState_clipboard goatState_withKeyboard of
                    Nothing    -> makeGoatCmdTempOutputFromNothing goatState_withKeyboard
                    Just stree -> r where

                      -- TODO this is totally wrong, it won't handle parent/children stuff correctly
                      -- TODO convert to MiniOwlTree :D
                      offsetstree = offsetSEltTree (V2 1 1) stree
                      minitree' = owlTree_fromSEltTree offsetstree
                      maxid1 = owlTree_maxId minitree' + 1
                      maxid2 = owlPFState_nextId (_owlPFWorkspace_owlPFState (_goatState_workspace goatState_withKeyboard))
                      minitree = owlTree_reindex (max maxid1 maxid2) minitree'
                      spot = lastPositionInSelection (goatState_owlTree goatState_withKeyboard) (_goatState_selection goatState_withKeyboard)
                      treePastaEv = WSEAddTree (spot, minitree)



                      r = makeGoatCmdTempOutputFromEvent (goatState_withKeyboard { _goatState_clipboard = Just offsetstree }) treePastaEv
                  KeyboardData (KeyboardKey_Char 'z') [KeyModifier_Ctrl] -> r where
                    r = makeGoatCmdTempOutputFromEvent goatState_withKeyboard WSEUndo
                  KeyboardData (KeyboardKey_Char 'y') [KeyModifier_Ctrl] -> r where
                    r = makeGoatCmdTempOutputFromEvent goatState_withKeyboard WSERedo
                  -- tool hotkeys
                  KeyboardData (KeyboardKey_Char key) _ -> r where
                    mtool = case key of
                      'v' -> Just Tool_Select
                      'p' -> Just Tool_Pan
                      'b' -> Just Tool_Box
                      'l' -> Just Tool_Line
                      't' -> Just Tool_Text
                      'n' -> Just Tool_TextArea
                      _   -> Nothing

                    newHandler = maybe (_goatState_handler goatState_withKeyboard) (makeHandlerFromNewTool goatState_withKeyboard) mtool
                    r = makeGoatCmdTempOutputFromNothing $ goatState_withKeyboard { _goatState_handler = newHandler }

                  -- unhandled input
                  _ -> makeGoatCmdTempOutputFromNothing goatState_withKeyboard

  -- | update OwlPFWorkspace from GoatCmdTempOutput |
  (workspace_afterEvent, cslmap_afterEvent) = case _goatCmdTempOutput_pFEvent goatCmdTempOutput of
    -- if there was no update, then changes are not valid
    Nothing   -> (_goatState_workspace goatState, IM.empty)
    Just (_, wsev) -> (r1,r2) where
      r1 = updateOwlPFWorkspace wsev (_goatState_workspace goatState)
      r2 = _owlPFWorkspace_lastChanges r1
  pFState_afterEvent = _owlPFWorkspace_owlPFState workspace_afterEvent

  -- | update pan from GoatCmdTempOutput |
  next_pan = case _goatCmdTempOutput_pan goatCmdTempOutput of
    Nothing -> _goatState_pan goatState
    Just (V2 dx dy) -> V2 (cx0+dx) (cy0 + dy) where
      V2 cx0 cy0 = _goatState_pan goatState

  -- | get layersState from GoatCmdTempOutput |
  next_layersState'' = case _goatCmdTempOutput_layersState goatCmdTempOutput of
    Nothing -> _goatState_layersState goatState
    Just ls -> ls

  -- | get selection from GoatCmdTempOutput |
  mSelectionFromPho = case _goatCmdTempOutput_select goatCmdTempOutput of
    Nothing -> Nothing
    --Just (add, sel) -> assert (superOwlParliament_isValid nextot r) $ Just r where
    Just (add, sel) -> assert (superOwlParliament_isValid nextot r) (Just r)where
      nextot = _owlPFState_owlTree pFState_afterEvent
      r' = if add
        then superOwlParliament_disjointUnionAndCorrect nextot (_goatState_selection goatState) sel
        else sel
      r = SuperOwlParliament . Seq.sortBy (owlTree_superOwl_comparePosition nextot) . unSuperOwlParliament $ r'

  -- | compute selection based on changes from updating OwlPFState (i.e. auto select newly created stuff if appropriate) |
  (isNewSelection', selectionAfterChanges) = if IM.null cslmap_afterEvent
    then (False, _goatState_selection goatState)
    else r where

      -- extract elements that got created
      newEltFoldMapFn rid v = case v of
        Nothing     -> []
        Just sowl -> if IM.member rid (_owlTree_mapping . _owlPFState_owlTree $ last_pFState) then [] else [sowl]

      -- NOTE, undoing a deleted element counts as a newly created element (and will be auto-selected)
      newlyCreatedSEltls = IM.foldMapWithKey newEltFoldMapFn cslmap_afterEvent

      sortedNewlyCreatedSEltls = SuperOwlParliament $ Seq.sortBy (owlTree_superOwl_comparePosition $ _owlPFState_owlTree $ pFState_afterEvent) (Seq.fromList newlyCreatedSEltls)
      -- pretty sure this does the same thing..
      --sortedNewlyCreatedSEltls = makeSortedSuperOwlParliament (_owlPFState_owlTree $ pFState_afterEvent) (Seq.fromList newlyCreatedSEltls)

      wasLoad = case cmd of
        GoatCmdLoad _ -> True
        _             -> False

      r = if wasLoad || null newlyCreatedSEltls
        -- if there are no newly created elts, we still need to update the selection
        then (\x -> (False, SuperOwlParliament x)) $ catMaybesSeq . flip fmap (unSuperOwlParliament (_goatState_selection goatState)) $ \sowl ->
          case IM.lookup (_superOwl_id sowl) cslmap_afterEvent of
            -- no changes means not deleted
            Nothing       -> Just sowl
            -- if deleted, remove it
            Just Nothing  -> Nothing
            -- it was changed, update selection to newest version
            Just (Just x) -> Just x
        else (True, sortedNewlyCreatedSEltls)

  -- for now, newly created stuff is the same as anything that got auto selected
  --newlyCreatedRids = IS.fromList . toList . fmap _superOwl_id . unSuperOwlParliament $ selectionAfterChanges

  -- | update the new selection based on previous computations|
  (isNewSelection, next_selection) = case mSelectionFromPho of
    Just x  -> assert (not isNewSelection') (True, x)
    -- better/more expensive check to ensure mSelectionFromPho stuff is mutually exclusive to selectionAfterChanges
    --Just x -> assert (selectionAfterChanges == _goatState_selection) (True, x)
    Nothing -> (isNewSelection', selectionAfterChanges)

  -- | update LayersState based from SuperOwlChanges after applying events |
  next_layersState' = updateLayers pFState_afterEvent cslmap_afterEvent next_layersState''

  -- | auto-expand folders and compute LayersState |
  -- auto expand folders for selected elements + (this will also auto expand when you drag or paste stuff into a folder)
  -- NOTE this will prevent you from ever collapsing a folder that has a selected child in it
  -- so maybe auto expand should only happen on newly created elements or add a way to detect for newly selected elements (e.g. diff between old selection)
  next_layersState = expandAllCollapsedParents next_selection pFState_afterEvent next_layersState'
  --next_layersState = next_layersState'


  -- | update the next handler |
  mHandlerFromPho = _goatCmdTempOutput_nextHandler goatCmdTempOutput
  filterHiddenOrLocked sowl = not $ layerMetaMap_isInheritHiddenOrLocked (_owlPFState_owlTree pFState_afterEvent) (_superOwl_id sowl) (_layersState_meta next_layersState)
  next_canvasSelection = superOwlParliament_convertToCanvasSelection (_owlPFState_owlTree pFState_afterEvent) filterHiddenOrLocked next_selection
  nextHandlerFromSelection = makeHandlerFromSelection next_canvasSelection
  next_handler' = if isNewSelection
    -- if there is a new selection, update the handler with new selection if handler wasn't active
    then maybeUpdateHandlerFromSelection (fromMaybe (SomePotatoHandler EmptyHandler) mHandlerFromPho) next_canvasSelection
    -- otherwise, use the returned handler or make a new one from selection
    else fromMaybe nextHandlerFromSelection mHandlerFromPho
  next_layersHandler' = goatCmdTempOutput_layersHandler goatCmdTempOutput
  (next_handler, next_layersHandler) = case _goatCmdTempOutput_pFEvent goatCmdTempOutput of


    -- TODO you only need to do this if handler is one that came from mHandlerFromPho
    -- if there was a non-canvas event, reset the handler D:
    -- since we don't have multi-user events, the handler should never be active when this happens
    Just (False, _) -> assert (not (pIsHandlerActive next_handler')) $ (refreshedHandler,refreshedLayersHandler) where
      -- CAREFUL INFINITE LOOP DANGER WITH USE OF `finalGoatState`
      -- safe for now, since `potatoHandlerInputFromGoatState` does not use `_goatState_handler/_goatState_layersHandler finalGoatState` which is set to `next_handler/next_layersHandler`
      next_potatoHandlerInput = potatoHandlerInputFromGoatState finalGoatState
      refreshedHandler = fromMaybe nextHandlerFromSelection ( pRefreshHandler next_handler' next_potatoHandlerInput)
      refreshedLayersHandler = fromMaybe (SomePotatoHandler (def :: LayersHandler)) (pRefreshHandler next_layersHandler' next_potatoHandlerInput)



    _ -> (next_handler', next_layersHandler')

  -- | TODO enter rename mode for newly created folders |
  -- TODO if cslmap_afterEvent has a newly created folder (i.e. we just createda folder) then we want to enter rename mode for that folder
    -- this is not correct, we want a condition for when we hit the "new folder" button. Perhaps there needs to be a separate command for enter rename and FE triggers 2 events in succession?
  --_goatState_layersHandler

  -- | update AttachmentMap based on new state and clear the cache on these changes |
  next_attachmentMap = updateAttachmentMapFromSuperOwlChanges cslmap_afterEvent (_goatState_attachmentMap goatState)
  -- we need to union with `_goatState_attachmentMap` as next_attachmentMap does not contain deleted targets and stuff we detached from
  attachmentMapForComputingChanges = IM.unionWith IS.union next_attachmentMap (_goatState_attachmentMap goatState)
  --attachmentChanges = trace "ATTACHMENTS" $ traceShow (IM.size cslmap_afterEvent) $ traceShowId $ getChangesFromAttachmentMap (_owlPFState_owlTree pFState_afterEvent) attachmentMapForComputingChanges cslmap_afterEvent
  attachmentChanges = getChangesFromAttachmentMap (_owlPFState_owlTree pFState_afterEvent) attachmentMapForComputingChanges cslmap_afterEvent

  -- | compute SuperOwlChanges for rendering |
  cslmap_withAttachments = IM.union cslmap_afterEvent attachmentChanges
  cslmap_fromLayersHide = _goatCmdTempOutput_changesFromToggleHide goatCmdTempOutput
  cslmap_forRendering = cslmap_fromLayersHide `IM.union` cslmap_withAttachments

  -- | clear the cache at places that have changed
  renderCache_resetOnChangesAndAttachments = renderCache_clearAtKeys (_goatState_renderCache goatState) (IM.keys cslmap_withAttachments)

  -- | update the BroadPhase
  (needsupdateaabbs, next_broadPhaseState) = update_bPTree (_owlPFState_owlTree pFState_afterEvent) cslmap_forRendering (_broadPhaseState_bPTree (_goatState_broadPhaseState goatState))

  -- | update the rendered region if we moved the screen |
  canvasRegionBox = LBox (-next_pan) (goatCmdTempOutput_screenRegion goatCmdTempOutput)
  newBox = canvasRegionBox
  didScreenRegionMove = _renderedCanvasRegion_box (_goatState_renderedCanvas goatState) /= newBox
  rendercontext_forMove = RenderContext {
      _renderContext_cache = renderCache_resetOnChangesAndAttachments
      , _renderContext_owlTree = _owlPFState_owlTree pFState_afterEvent
      , _renderContext_layerMetaMap = _layersState_meta next_layersState
      , _renderContext_broadPhase = next_broadPhaseState
      , _renderContext_renderedCanvasRegion = _goatState_renderedCanvas goatState
    }
  rendercontext_forUpdate = if didScreenRegionMove
    then moveRenderedCanvasRegion newBox rendercontext_forMove
    else rendercontext_forMove

  -- | render the scene if there were changes, note that updates from actual changes are mutually exclusive from updates due to panning (although I think it would still work even if it weren't) |
  rendercontext_afterUpdate = if IM.null cslmap_forRendering
    then rendercontext_forUpdate
    else updateCanvas cslmap_forRendering needsupdateaabbs rendercontext_forUpdate

  next_renderedCanvas = _renderContext_renderedCanvasRegion rendercontext_afterUpdate

  -- | render the selection |
  rendercontext_forSelection = rendercontext_afterUpdate {
      -- NOTE this will render hidden stuff that's selected via layers!!
      _renderContext_layerMetaMap = IM.empty

      -- TODO DELETE THIS YOU SHOULDN'T HAVE TO DO THIS, this is breaking caching (you can fix by commenting it out)
      -- IDK WHY BUT IF YOU SELECT AUTOLINE WITH BOX AND MOVE BOTH THE CACHE STAYS WITH ORIGINAL PLACE AND SELECTED LINE DOESN'T MOVE
      -- so temp fix it by reseting the cache on attached lines whos target moved
      , _renderContext_cache = renderCache_resetOnChangesAndAttachments

      -- empty canvas to render our selection in
      -- we just re-render everything for now (in the future you can try and do partial rendering though)
      , _renderContext_renderedCanvasRegion = emptyRenderedCanvasRegion newBox
    }
  selectionselts = toList . fmap _superOwl_id $ unSuperOwlParliament next_selection

  (next_renderedSelection, next_renderCache) = if _goatState_selection goatState == next_selection && not didScreenRegionMove && IM.null cslmap_forRendering
    -- nothing changed, we can keep our selection rendering
    then (_goatState_renderedSelection goatState, _renderContext_cache rendercontext_afterUpdate)
    else (_renderContext_renderedCanvasRegion rctx, _renderContext_cache rctx) where
      rctx = render_new newBox selectionselts rendercontext_forSelection

  -- TODO just DELETE this...
  {- TODO render only parts of selection that have changed TODO broken
  next_renderedSelection' = if didScreenRegionMove
    then moveRenderedCanvasRegion next_broadPhaseState (owlTree_withCacheResetOnAttachments) newBox _goatState_renderedSelection
    else _goatState_renderedSelection
  prevSelChangeMap = IM.fromList . toList . fmap (\sowl -> (_superOwl_id sowl, Nothing)) $ unSuperOwlParliament _goatState_selection
  curSelChangeMap = IM.fromList . toList . fmap (\sowl -> (_superOwl_id sowl, Just sowl)) $ unSuperOwlParliament next_selection
  -- TODO you can be even smarter about this by combining cslmap_forRendering I think
  cslmapForSelectionRendering = curSelChangeMap `IM.union` prevSelChangeMap
  -- you need to do something like this but this is wrong....
  --(needsupdateaabbsforrenderselection, _) = update_bPTree cslmapForSelectionRendering (_broadPhaseState_bPTree next_broadPhaseState)
  needsupdateaabbsforrenderselection = needsupdateaabbs
  next_renderedSelection = if IM.null cslmapForSelectionRendering
    then next_renderedSelection'
    else updateCanvas cslmapForSelectionRendering needsupdateaabbsforrenderselection next_broadPhaseState pFState_withCacheResetOnAttachments next_renderedSelection'
  -}

  next_pFState = pFState_afterEvent { _owlPFState_owlTree = _renderContext_owlTree rendercontext_forSelection }
  next_workspace = workspace_afterEvent { _owlPFWorkspace_owlPFState = next_pFState}

  checkAttachmentMap = owlTree_makeAttachmentMap (_owlPFState_owlTree next_pFState) == next_attachmentMap

  -- TODO remove assert in production builds
  finalGoatState = if not checkAttachmentMap
    then error $ (show (owlTree_makeAttachmentMap (_owlPFState_owlTree next_pFState))) <> "\n\n\n" <> show next_attachmentMap
    else
      (_goatCmdTempOutput_goatState goatCmdTempOutput) {
        _goatState_workspace      = next_workspace
        , _goatState_pan             = next_pan
        , _goatState_layersHandler  = next_layersHandler
        , _goatState_handler         = next_handler
        , _goatState_selection       = next_selection
        , _goatState_canvasSelection = next_canvasSelection
        , _goatState_broadPhaseState = next_broadPhaseState
        , _goatState_renderedCanvas = next_renderedCanvas
        , _goatState_renderedSelection = next_renderedSelection
        , _goatState_layersState     = next_layersState
        , _goatState_attachmentMap = next_attachmentMap
        , _goatState_renderCache = next_renderCache
      }