{-| Module : Client.Image.Layout Description : Layout code for the multi-window splits Copyright : (c) Eric Mertens, 2016 License : ISC Maintainer : emertens@gmail.com -} module Client.Image.Layout (scrollAmount, drawLayout) where import Client.Configuration (LayoutMode(..)) import Client.Image.LineWrap (lineWrap, terminate) import Client.Image.PackedImage (Image', unpackImage) import Client.Image.Palette (palWindowDivider) import Client.Image.StatusLine (statusLineImage, minorStatusLineImage) import Client.Image.Textbox (textboxImage) import Client.State import Client.State.Focus ( Focus, Subfocus ) import Client.View (viewLines) import Control.Lens (view) import Graphics.Vty.Attributes (defAttr) import Graphics.Vty.Image -- | Compute the combined image for all the visible message windows. drawLayout :: ClientState {- ^ client state -} -> (Int, Int, Int, Int, Image) {- ^ overscroll, cursor row, cursor col, next offset, final image -} drawLayout st = case view clientLayout st of TwoColumn | not (null extrafocus) -> drawLayoutTwo st extrafocus _ -> drawLayoutOne st extrafocus where extrafocus = clientExtraFocuses st -- | Layout algorithm for all windows in a single column. drawLayoutOne :: ClientState {- ^ client state -} -> [(Focus, Subfocus)] {- ^ extra windows -} -> (Int, Int, Int, Int, Image) {- ^ overscroll and final image -} drawLayoutOne st extrafocus = (overscroll, row, col, nextOffset, output) where w = view clientWidth st h:hs = splitHeights (rows - saveRows) (length extraLines) scroll = view clientScroll st (overscroll, row, col, nextOffset, main) = drawMain w (saveRows + h) scroll st output = vertCat $ reverse $ main : [ drawExtra st w h' foc subfoc imgs | (h', (foc, subfoc, imgs)) <- zip hs extraLines] rows = view clientHeight st -- don't count textbox or the main status line against the main window's height saveRows = 1 + imageHeight (statusLineImage w st) extraLines = [ (focus, subfocus, viewLines focus subfocus w st) | (focus, subfocus) <- extrafocus ] -- | Layout algorithm for all windows in a single column. drawLayoutTwo :: ClientState {- ^ client state -} -> [(Focus, Subfocus)] {- ^ extra windows -} -> (Int, Int, Int, Int, Image) {- ^ overscroll, cursor row, cursor col, offset, final image -} drawLayoutTwo st extrafocus = (overscroll, row, col, nextOffset, output) where [wl,wr] = divisions (view clientWidth st - 1) 2 hs = divisions (rows - length extraLines) (length extraLines) scroll = view clientScroll st output = main <|> divider <|> extraImgs extraImgs = vertCat $ reverse [ drawExtra st wr h' foc subfoc imgs | (h', (foc, subfoc, imgs)) <- zip hs extraLines] (overscroll, row, col, nextOffset, main) = drawMain wl rows scroll st pal = clientPalette st divider = charFill (view palWindowDivider pal) ' ' 1 rows rows = view clientHeight st extraLines = [ (focus, subfocus, viewLines focus subfocus wr st) | (focus, subfocus) <- extrafocus ] drawMain :: Int {- ^ draw width -} -> Int {- ^ draw height -} -> Int {- ^ scroll amount -} -> ClientState {- ^ client state -} -> (Int,Int,Int,Int,Image) drawMain w h scroll st = (overscroll, row, col, nextOffset, msgs <-> bottomImg) where focus = view clientFocus st subfocus = view clientSubfocus st msgLines = viewLines focus subfocus w st (overscroll, msgs) = messagePane w h' scroll msgLines h' = max 0 (h - imageHeight bottomImg) bottomImg = statusLineImage w st <-> tbImage (row, col, nextOffset, tbImage) = textboxImage 3 w st -- | Draw one of the extra windows from @/splits@ drawExtra :: ClientState {- ^ client state -} -> Int {- ^ draw width -} -> Int {- ^ draw height -} -> Focus {- ^ focus -} -> Subfocus {- ^ subfocus -} -> [Image'] {- ^ image lines -} -> Image {- ^ rendered window -} drawExtra st w h focus subfocus lineImages = msgImg <-> unpackImage (minorStatusLineImage focus subfocus w True st) where (_, msgImg) = messagePane w h 0 lineImages -- | Generate an image corresponding to the image lines of the given -- focus and subfocus. Returns the number of lines overscrolled to -- assist in clamping scroll to the lines available in the window. messagePane :: Int {- ^ client width -} -> Int {- ^ available rows -} -> Int {- ^ current scroll -} -> [Image'] {- ^ focused window -} -> (Int, Image) {- ^ overscroll, rendered messages -} messagePane w h scroll images = (overscroll, img) where vimg = assemble emptyImage images vimg1 = cropBottom h vimg img = charFill defAttr ' ' w (h - imageHeight vimg1) <-> vimg1 overscroll = vh - imageHeight vimg vh = h + scroll assemble acc _ | imageHeight acc >= vh = cropTop vh acc assemble acc [] = acc assemble acc (x:xs) = assemble (this <-> acc) xs where this = vertCat $ map (terminate w . unpackImage) $ lineWrap w x splitHeights :: Int {- ^ screen rows to fill -} -> Int {- ^ number of extra windows -} -> [Int] {- ^ list of heights for each division -} splitHeights h ex = divisions (h - ex) (1 + ex) -- | Constructs a list of numbers with the length of the divisor -- and that sums to the dividend. Each element will be within -- one of the quotient. divisions :: Int {- ^ dividend -} -> Int {- ^ divisor -} -> [Int] divisions x y | y <= 0 = [] | otherwise = replicate r (q+1) ++ replicate (y-r) q where (q,r) = quotRem (max 0 x) y -- | Compute the number of lines in a page at the current window size scrollAmount :: ClientState {- ^ client state -} -> Int {- ^ scroll amount -} scrollAmount st = case view clientLayout st of TwoColumn -> h OneColumn -> head (splitHeights h ex) -- extra will be equal to main or 1 smaller where layout = view clientLayout st h = view clientHeight st - bottomSize ex = length (clientExtraFocuses st) bottomSize = 1 -- textbox + imageHeight (statusLineImage mainWidth st) mainWidth = case layout of TwoColumn -> head (divisions (view clientWidth st - 1) 2) OneColumn -> view clientWidth st