packages feed

tinytools-vty-0.1.0.0: src/Potato/Flow/Vty/Canvas.hs

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

module Potato.Flow.Vty.Canvas (
  CanvasWidgetConfig(..)
  , CanvasWidget(..)
  , holdCanvasWidget
) where


import           Relude

import           Potato.Flow
import           Potato.Flow.Vty.Input
import           Potato.Reflex.Vty.Helpers
import Potato.Flow.Vty.PotatoReader

import qualified Data.Text as T
import qualified Data.List.Index as L
import Data.Tuple.Extra (thd3)

import qualified Graphics.Vty                   as V
import           Reflex
import           Reflex.Vty


-- TODO why does this not handle wide chars correctly?
-- alternative text rendering methods that don't show spaces
textNoRenderSpaces
  :: (HasDisplayRegion t m, HasImageWriter t m, HasTheme t m)
  => Behavior t Text
  -> m ()
textNoRenderSpaces t = do
  bt <- theme
  let img = (\a s -> [makeimages a s])
        <$> bt
        <*> t
  tellImages (fmap join img)
  where
    -- revout is of type [(Text, Int)] where the int is offset from BoL
    foldlinefn (offset, spaces, revout) c = (offset+ (fromIntegral (getPCharWidth c)), newspaces, newrevout) where
      (newspaces, newrevout) = if c == ' '
        then (spaces+1, revout)
        else if spaces /= 0
          then (0, ([c], offset):revout)
          else case revout of
            (x,n):xs -> (0, (c:x,n):xs)
            -- first character case
            [] -> (0, [([c], 0)])
    makeimages th =
      join
      . L.imap (\i -> fmap (V.translateY i)) -- for each line, offset the image vertically
      . fmap (fmap (\(txt,offset) -> V.translateX offset $  V.string th (reverse txt))) -- for each chunk and offset, convert to image
      . fmap (thd3 . foldl' foldlinefn (0,0,[]) . T.unpack) -- for each line, split into chunks with offset
      . T.split (=='\n') -- split into lines

lBox_to_region :: LBox -> Region
lBox_to_region (LBox (V2 x y) (V2 w h)) = Region x y w h

region_to_lBox :: Region -> LBox
region_to_lBox (Region x y w h) = (LBox (V2 x y) (V2 w h))

dynLBox_to_dynRegion :: (Reflex t) => Dynamic t LBox -> Dynamic t Region
dynLBox_to_dynRegion dlb = ffor dlb $ lBox_to_region

{- DELETE ME
translate_dynRegion :: (Reflex t) => Dynamic t XY -> Dynamic t Region -> Dynamic t Region
translate_dynRegion dpos dr = ffor2 dpos dr $ \(V2 x y) region -> region {
    _region_left = _region_left region + x
    , _region_top = _region_top region + y
  }
-}

data CanvasWidgetConfig t = CanvasWidgetConfig {
  _canvasWidgetConfig_pan            :: Dynamic t XY
  -- TODO DELETE
  , _canvasWidgetConfig_broadPhase     :: Dynamic t BroadPhaseState
  , _canvasWidgetConfig_renderedCanvas :: Dynamic t RenderedCanvasRegion
  , _canvasWidgetConfig_renderedSelection :: Dynamic t RenderedCanvasRegion
  , _canvasWidgetConfig_canvas         :: Dynamic t SCanvas
  , _canvasWidgetConfig_handles        :: Dynamic t HandlerRenderOutput
}


data CanvasWidget t = CanvasWidget {
  _canvasWidget_mouse :: Event t LMouseData
  , _canvasWidget_regionDim :: Event t XY
}

holdCanvasWidget :: forall t m. (MonadWidget t m, HasPotato t m)
  => CanvasWidgetConfig t
  -> m (CanvasWidget t)
holdCanvasWidget CanvasWidgetConfig {..} = mdo

  potatostylebeh <- fmap _potatoConfig_style askPotato

  dh <- displayHeight
  dw <- displayWidth

  -- ::draw the canvas::
  let
    -- the screen region
    screenRegion' = ffor2 dw dh (\w h -> LBox 0 (V2 w h))
    screenRegion = dynLBox_to_dynRegion screenRegion'
    -- the screen region in canvas space
    canvasScreenRegion' = fmap _renderedCanvasRegion_box _canvasWidgetConfig_renderedCanvas

    -- true region is the canvas region cropped to the panned screen (i.e. the intersection of screen and canvas in canvas space)
    maybeCropAndPan pan scanvas screen = maybe (LBox 0 0) (translate_lBox pan) $ intersect_lBox screen (_sCanvas_box scanvas)
    trueRegion' = ffor3 _canvasWidgetConfig_pan _canvasWidgetConfig_canvas canvasScreenRegion' maybeCropAndPan
    trueRegion = dynLBox_to_dynRegion trueRegion'
    oobRegions' = ffor2 screenRegion' trueRegion' $ \sc tr -> substract_lBox sc tr
    oobRegions = fmap (fmap lBox_to_region) oobRegions'

    -- reg is in screen space so we need to translate back to canvas space by undoing the pan
    renderRegionFn pan reg rc = renderedCanvasRegionToText (translate_lBox (-pan) (region_to_lBox reg)) rc

    -- same as renderRegionFn
    debugRenderRegionFn pan reg rc = r where
      txt = renderedCanvasRegionToText (translate_lBox (-pan) (region_to_lBox reg)) rc
      --r = trace (T.unpack txt) txt
      r = txt

    renderRegion dreg = pane dreg (constDyn False) $ do
      text . current . ffor3 _canvasWidgetConfig_pan dreg _canvasWidgetConfig_renderedCanvas $ renderRegionFn


  -- 1. render out of bounds region
  localTheme (const (fmap _potatoStyle_canvas_oob potatostylebeh)) $ do
    fill (constant ' ')
    simpleList oobRegions renderRegion
    return ()

  -- 2. render the canvas region
  renderRegion trueRegion

  -- 3. render the selection
  localTheme (const (fmap _potatoStyle_selected potatostylebeh)) $ do
    -- this version does not handle wide chars correctly
    textNoRenderSpaces . current . ffor3 _canvasWidgetConfig_pan screenRegion _canvasWidgetConfig_renderedSelection $ debugRenderRegionFn
    return ()

  -- 4. render the handlers
  let
    makerhimage attr' (V2 px py) rh = r where
      LBox (V2 x y) (V2 w h) = _renderHandle_box rh
      rc = fromMaybe ' ' $ _renderHandle_char rh
      attr = attr' -- TODO eventually RenderHandle may be styled somehow
      r = V.translate (x+px) (y+py) $ V.charFill attr rc w h
  tellImages $ ffor3 (current _canvasWidgetConfig_handles) (fmap _potatoStyle_makeCanvasManipulator potatostylebeh) (current _canvasWidgetConfig_pan)
    $ \(HandlerRenderOutput hs) attrfn reg -> fmap (\rh -> makerhimage (attrfn (_renderHandle_color rh)) reg rh) hs

  inp <- makeLMouseDataInputEv (constDyn (0,0)) False
  postBuildEv <- getPostBuild

  let
    regionDimDyn = ffor2 dw dh V2
    regionDimEv = updated regionDimDyn
    forceDimEv = pushAlways (\_ -> sample . current $ regionDimDyn) postBuildEv


  return $ CanvasWidget inp (leftmost [regionDimEv, forceDimEv])