{-| Module : Monomer.Widgets.Util.Drawing Copyright : (c) 2018 Francisco Vallarino License : BSD-3-Clause (see the LICENSE file) Maintainer : fjvallarino@gmail.com Stability : experimental Portability : non-portable Utility drawing functions. Built on top the lower level primitives provided by "Monomer.Graphics.Types.Renderer". -} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE Strict #-} module Monomer.Widgets.Util.Drawing ( drawInScissor, drawInTranslation, drawInScale, drawInRotation, drawInAlpha, drawTextLine, drawLine, drawRect, drawRectBorder, drawRectBoxGradient, drawTriangle, drawTriangleBorder, drawArc, drawArcBorder, drawEllipse, drawEllipseBorder, drawArrowUp, drawArrowDown, drawTimesX, drawStyledAction, drawStyledAction_, drawRoundedRect, drawRoundedRectBorder ) where import Control.Applicative ((<|>)) import Control.Lens ((^.), (^?!), non) import Control.Monad (forM_, when) import Data.Default import Data.Maybe import Monomer.Core import Monomer.Graphics.Types import qualified Monomer.Common.Lens as L import qualified Monomer.Core.Lens as L -- | Performs the provided drawing operations with an active scissor, and then -- | disables it. drawInScissor :: Renderer -- ^ The renderer. -> Bool -- ^ Whether to apply the scissor (useful to selectively apply). -> Rect -- ^ Scissor rect, where drawing will be visible. -> IO () -- ^ Drawing operations. -> IO () -- ^ The resulting action. drawInScissor renderer False _ action = action drawInScissor renderer True rect action = do saveContext renderer intersectScissor renderer rect action restoreContext renderer -- | Performs the provided drawing operations displaced by the given offset. drawInTranslation :: Renderer -- ^ The renderer. -> Point -- ^ The offset to apply. -> IO () -- ^ Drawing operations. -> IO () -- ^ The resulting action. drawInTranslation renderer offset action = do saveContext renderer setTranslation renderer offset action restoreContext renderer -- | Performs the provided drawing operations with the given resize scale. drawInScale :: Renderer -- ^ The renderer. -> Point -- ^ The horizontal and vertical scale factor to apply. -> IO () -- ^ Drawing operations. -> IO () -- ^ The resulting action. drawInScale renderer scale action = do saveContext renderer setScale renderer scale action restoreContext renderer -- | Performs the provided drawing operations with the given rotation angle. drawInRotation :: Renderer -- ^ The renderer. -> Double -- ^ The angle in degrees. -> IO () -- ^ Drawing operations. -> IO () -- ^ The resulting action. drawInRotation renderer angle action = do saveContext renderer setRotation renderer angle action restoreContext renderer -- | Performs the provided drawing operations with a global alpha applied. drawInAlpha :: Renderer -- ^ The renderer. -> Double -- ^ The global alpha to apply. -> IO () -- ^ Drawing operations. -> IO () -- ^ The resulting action. drawInAlpha renderer alpha action = do saveContext renderer setGlobalAlpha renderer alpha action restoreContext renderer -- | Draws a TextLine with the provided style. Font and size must be the same -- | as when the TextLine was created, but color and decorations can change. drawTextLine :: Renderer -- ^ The renderer. -> StyleState -- ^ The style to apply. -> TextLine -- ^ The TextLine with the text to render. -> IO () -- ^ The resulting action. drawTextLine renderer style textLine = do setFillColor renderer fontColor renderText renderer txtOrigin _tlFont _tlFontSize _tlFontSpaceH _tlText when underline $ do drawLine renderer (Point tx uy) (Point tr uy) lw (Just fontColor) when overline $ do drawLine renderer (Point tx oy) (Point tr oy) lw (Just fontColor) when throughline $ do drawLine renderer (Point tx hy) (Point tr hy) lw (Just fontColor) where TextLine{..} = textLine TextMetrics asc desc _ _ = _tlMetrics Rect tx ty tw th = _tlRect tr = tx + tw fontColor = styleFontColor style alignV = styleTextAlignV style underline = style ^?! L.text . non def . L.underline . non False overline = style ^?! L.text . non def . L.overline . non False throughline = style ^?! L.text . non def . L.throughline . non False offset | alignV == ATBaseline = 0 | otherwise = desc {- There's not a scientific reason for choosing 1/20 as the scale, it just looked reasonably good as the line width on a set of different fonts. -} lw = max 1.5 (unFontSize _tlFontSize / 20) by = ty + th + offset uy = by + 1.5 * lw oy = ty hy = by - asc * 0.35 txtOrigin = Point tx by -- | Draws a line with the given width and color. drawLine :: Renderer -- ^ The renderer. -> Point -- ^ The start point. -> Point -- ^ The end point. -> Double -- ^ The line width. -> Maybe Color -- ^ The color. If Nothing, the line will not be drawn. -> IO () -- ^ The resulting action. drawLine _ _ _ _ Nothing = pure () drawLine renderer p1 p2 width (Just color) = do beginPath renderer setStrokeColor renderer color setStrokeWidth renderer width renderLine renderer p1 p2 stroke renderer -- | Draws a filled rect with the given color and radius. drawRect :: Renderer -- ^ The renderer. -> Rect -- ^ The rectangle to be drawn. -> Maybe Color -- ^ The color. If Nothing, the rect will not be drawn. -> Maybe Radius -- ^ The optional radius config. -> IO () -- ^ The resulting action. drawRect _ _ Nothing _ = pure () drawRect renderer rect (Just color) Nothing = do beginPath renderer setFillColor renderer color renderRect renderer rect fill renderer drawRect renderer rect (Just color) (Just radius) = do beginPath renderer setFillColor renderer color drawRoundedRect renderer rect radius fill renderer -- | Draws a rect's border, with an optional radius. drawRectBorder :: Renderer -- ^ The renderer. -> Rect -- ^ The rectangle to be drawn. -> Border -- ^ The border config. -> Maybe Radius -- ^ The optional radius config. -> IO () -- ^ The resulting action. drawRectBorder renderer rect border Nothing = drawRectSimpleBorder renderer rect border drawRectBorder renderer rect border (Just radius) = drawRoundedRectBorder renderer rect border radius -- | Draws a box gradient rect with the given radius, inner and outer colors. drawRectBoxGradient :: Renderer -- ^ The renderer. -> Rect -- ^ The rectangle to be drawn. -> Double -- ^ The radius width. -> Double -- ^ The feather size. -> Color -- ^ The inner color. -> Color -- ^ The outer color. -> IO () -- ^ The resulting action. drawRectBoxGradient renderer rect rad feather inner outer = do beginPath renderer renderRoundedRect renderer rect rad rad rad rad setFillBoxGradient renderer rect rad feather inner outer fill renderer where Rect rx ry rw rh = rect {-| Draws a filled triangle with the given color. The points should be provided in clockwise order. -} drawTriangle :: Renderer -- ^ The renderer. -> Point -- ^ The first point. -> Point -- ^ The second point. -> Point -- ^ The third point. -> Maybe Color -- ^ The color. If Nothing, the triangle will not be drawn. -> IO () -- ^ The resulting action. drawTriangle _ _ _ _ Nothing = return () drawTriangle renderer p1 p2 p3 (Just color) = do beginPath renderer setFillColor renderer color moveTo renderer p1 renderLineTo renderer p2 renderLineTo renderer p3 closePath renderer fill renderer -- | Draws a triangle's border with the given color and width. drawTriangleBorder :: Renderer -- ^ The renderer. -> Point -- ^ The first point. -> Point -- ^ The second point. -> Point -- ^ The third point. -> Double -- ^ The border width. -> Maybe Color -- ^ The color. If Nothing, the triangle will not be drawn. -> IO () -- ^ The resulting action. drawTriangleBorder _ _ _ _ _ Nothing = return () drawTriangleBorder renderer p1 p2 p3 width (Just color) = when (width > 0) $ do beginPath renderer setStrokeColor renderer color setStrokeWidth renderer width moveTo renderer p1 renderLineTo renderer p2 renderLineTo renderer p3 closePath renderer stroke renderer -- | Draws a filled arc, delimited by a rect and within the given angles. drawArc :: Renderer -- ^ The renderer. -> Rect -- ^ The rect delimiting the arc area. -> Double -- ^ The start angle in degrees. -> Double -- ^ The end angle in degrees. -> Winding -- ^ The direction in which the arc is drawn. -> Maybe Color -- ^ The color. If Nothing, the arc will not be drawn. -> IO () -- ^ The resulting action. drawArc renderer rect start end winding Nothing = return () drawArc renderer rect start end winding (Just color) = do beginPath renderer setFillColor renderer color renderArc renderer center radius start end winding fill renderer where Rect rx ry rw rh = rect radius = min (rw / 2) (rh / 2) center = Point (rx + rw / 2) (ry + rh / 2) -- | Draws an arc's border, delimited by a rect and within the given angles. drawArcBorder :: Renderer -- ^ The renderer. -> Rect -- ^ The rect delimiting the arc area. -> Double -- ^ The start angle in degrees. -> Double -- ^ The end angle in degrees. -> Winding -- ^ The direction in which the arc is drawn. -> Maybe Color -- ^ The color. If Nothing, the arc will not be drawn. -> Double -- ^ The arc width. -> IO () -- ^ The resulting action. drawArcBorder renderer rect start end winding Nothing width = return () drawArcBorder renderer rect start end winding (Just color) width = when (width > 0) $ do beginPath renderer setStrokeColor renderer color setStrokeWidth renderer width renderArc renderer center radius start end winding stroke renderer where Rect rx ry rw rh = rect radius = min ((rw - width) / 2) ((rh - width) / 2) center = Point (rx + rw / 2) (ry + rh / 2) -- | Draws a filled ellipse, delimited by a rect. drawEllipse :: Renderer -- ^ The renderer. -> Rect -- ^ The rect delimiting the ellipse. -> Maybe Color -- ^ The color. If Nothing, the ellipse will not be drawn. -> IO () -- ^ The resulting action. drawEllipse renderer rect Nothing = return () drawEllipse renderer rect (Just color) = do beginPath renderer setFillColor renderer color renderEllipse renderer rect fill renderer -- | Draws an ellipse's border, delimited by a rect. drawEllipseBorder :: Renderer -- ^ The renderer. -> Rect -- ^ The rect delimiting the ellipse. -> Maybe Color -- ^ The color. If Nothing, the ellipse will not be drawn. -> Double -- ^ The border width. -> IO () -- ^ The resulting action. drawEllipseBorder renderer rect Nothing _ = return () drawEllipseBorder renderer rect (Just color) width = when (width > 0) $ forM_ contentRect $ \finalRect -> do beginPath renderer setStrokeColor renderer color setStrokeWidth renderer width renderEllipse renderer finalRect stroke renderer where contentRect = subtractFromRect rect w w w w w = width / 2 -- | Draws a triangular arrow pointing up, delimited by the given rect. drawArrowUp :: Renderer -- ^ The renderer. -> Rect -- ^ The rect delimiting the arrow. -> Maybe Color -- ^ The color. If Nothing, the arrow will not be drawn. -> IO () -- ^ The resulting action. drawArrowUp renderer rect Nothing = return () drawArrowUp renderer rect (Just color) = do beginPath renderer setFillColor renderer color moveTo renderer p1 renderLineTo renderer p2 renderLineTo renderer p3 renderLineTo renderer p1 fill renderer where Rect x y w h = rect p1 = Point (x + w / 2) y p2 = Point (x + w) (y + h) p3 = Point x (y + h) -- | Draws a triangular arrow pointing down, delimited by the given rect. drawArrowDown :: Renderer -- ^ The renderer. -> Rect -- ^ The rect delimiting the arrow. -> Maybe Color -- ^ The color. If Nothing, the arrow will not be drawn. -> IO () -- ^ The resulting action. drawArrowDown renderer rect Nothing = return () drawArrowDown renderer rect (Just color) = do beginPath renderer setFillColor renderer color moveTo renderer p1 renderLineTo renderer p2 renderLineTo renderer p3 renderLineTo renderer p1 fill renderer where Rect x y w h = rect p1 = Point x y p2 = Point (x + w) y p3 = Point (x + w / 2) (y + h) -- | Draws an X, delimited by the given rect. drawTimesX :: Renderer -- ^ The renderer. -> Rect -- ^ The rect delimiting the arrow. -> Double -- ^ The width of the lines. -> Maybe Color -- ^ The color. If Nothing, the X will not be drawn. -> IO () -- ^ The resulting action. drawTimesX renderer rect lw Nothing = return () drawTimesX renderer rect lw (Just fgColor) = do beginPath renderer setFillColor renderer fgColor moveTo renderer (Point (x + hw) y) renderLineTo renderer (Point cx (cy - hw)) renderLineTo renderer (Point (mx - hw) y) renderLineTo renderer (Point mx (y + hw)) renderLineTo renderer (Point (cx + hw) cy) renderLineTo renderer (Point mx (my - hw)) renderLineTo renderer (Point (mx - hw) my) renderLineTo renderer (Point cx (cy + hw)) renderLineTo renderer (Point (x + hw) my) renderLineTo renderer (Point x (my - hw)) renderLineTo renderer (Point (cx - hw) cy) renderLineTo renderer (Point x (y + hw)) renderLineTo renderer (Point (x + hw) y) fill renderer where Rect x y w h = rect hw = lw / 2 cx = x + w / 2 cy = y + h / 2 mx = x + w my = y + h {-| Runs a set of rendering operations after drawing the style's background, and before drawing the style's border. -} drawStyledAction :: Renderer -- ^ The renderer. -> Rect -- ^ The rect where background and border will be drawn. -> StyleState -- ^ The style defining background and border. -> (Rect -> IO ()) -- ^ The drawing actions. They receive the content area. -> IO () -- ^ The resulting action. drawStyledAction renderer rect style action = drawStyledAction_ renderer True rect style action {-| Runs a set of rendering operations after conditionally drawing the style's background, and before drawing the style's border. -} drawStyledAction_ :: Renderer -- ^ The renderer. -> Bool -- ^ Whether background and border should be drawn. -> Rect -- ^ The rect where background and border will be drawn. -> StyleState -- ^ The style defining background and border. -> (Rect -> IO ()) -- ^ The drawing actions. They receive the content area. -> IO () -- ^ The resulting action. drawStyledAction_ renderer drawDecorations rect style action = do when drawDecorations $ drawRect renderer rect _sstBgColor _sstRadius forM_ contentRect action when (drawDecorations && isJust _sstBorder) $ drawRectBorder renderer rect (fromJust _sstBorder) _sstRadius where StyleState{..} = style contentRect = removeOuterBounds style rect -- | Draws a rounded rect with the provided radius config. drawRoundedRect :: Renderer -> Rect -> Radius -> IO () drawRoundedRect renderer rect radius = let Rect _ _ w h = rect Radius{..} = fixRadius rect radius midw = min w h / 2 validTL = min midw (radW _radTopLeft) validTR = min midw (radW _radTopRight) validBR = min midw (radW _radBottomRight) validBL = min midw (radW _radBottomLeft) in do renderRoundedRect renderer rect validTL validTR validBR validBL drawRectSimpleBorder :: Renderer -> Rect -> Border -> IO () drawRectSimpleBorder renderer (Rect x y w h) Border{..} = let ptl = Point x y ptr = Point (x + w) y pbr = Point (x + w) (y + h) pbl = Point x (y + h) borderL = _brdLeft borderR = _brdRight borderT = _brdTop borderB = _brdBottom in do (olt, otl, itl) <- drawRectCorner renderer CornerTL ptl borderL borderT (otr, ort, itr) <- drawRectCorner renderer CornerTR ptr borderT borderR (orb, obr, ibr) <- drawRectCorner renderer CornerBR pbr borderR borderB (obl, olb, ibl) <- drawRectCorner renderer CornerBL pbl borderB borderL drawQuad renderer otl otr itr itl borderT drawQuad renderer ort orb ibr itr borderR drawQuad renderer obr obl ibl ibr borderB drawQuad renderer olb olt itl ibl borderL drawRectCorner :: Renderer -> RectCorner -> Point -> Maybe BorderSide -> Maybe BorderSide -> IO (Point, Point, Point) drawRectCorner _ _ ocorner Nothing Nothing = return points where points = (ocorner, ocorner, ocorner) drawRectCorner renderer cor ocorner ms1 ms2 = do when (w1 > 0 && w2 > 0) $ do beginPath renderer if color1 == color2 then setFillColor renderer color1 else setFillLinearGradient renderer g1 g2 color1 color2 moveTo renderer o1 renderLineTo renderer icorner renderLineTo renderer o2 renderLineTo renderer ocorner closePath renderer fill renderer return (o1, o2, icorner) where Point cx cy = ocorner s1 = fromMaybe def ms1 s2 = fromMaybe def ms2 w1 = _bsWidth s1 w2 = _bsWidth s2 color1 = _bsColor (fromJust (ms1 <|> ms2)) color2 = _bsColor (fromJust (ms2 <|> ms1)) (o1, o2) = case cor of CornerTL -> (Point cx (cy + w2), Point (cx + w1) cy) CornerTR -> (Point (cx - w2) cy, Point cx (cy + w1)) CornerBR -> (Point cx (cy - w2), Point (cx - w1) cy) CornerBL -> (Point (cx + w2) cy, Point cx (cy - w1)) icorner = case cor of CornerTL -> Point (cx + w1) (cy + w2) CornerTR -> Point (cx - w2) (cy + w1) CornerBR -> Point (cx - w1) (cy - w2) CornerBL -> Point (cx + w2) (cy - w1) (g1, g2) = cornerGradientPoints ocorner icorner -- | Draws the border of a rounded rect. Borders' widths may not match. drawRoundedRectBorder :: Renderer -> Rect -> Border -> Radius -> IO () drawRoundedRectBorder renderer rect border radius = let Rect xl yt w h = rect Border borL borR borT borB = border Radius radTL radTR radBL radBR = fixRadius rect radius xr = xl + w yb = yt + h hw = w / 2 hh = h / 2 midw = min w h / 2 rtl = Rect xl yt hw hh rtr = Rect (xl + hw) yt hw hh rbr = Rect (xl + hw) (yt + hh) hw hh rbl = Rect xl (yt + hh) hw hh validTL = min midw (radW radTL) validTR = min midw (radW radTR) validBR = min midw (radW radBR) validBL = min midw (radW radBL) xt1 = xl + validTL xt2 = xr - validTR xb1 = xl + validBL xb2 = xr - validBR yl1 = yt + validTL yl2 = yb - validBL yr1 = yt + validTR yr2 = yb - validBR in do (lt1, lt2, tl1, tl2) <- drawRoundedCorner renderer CornerTL rtl (p2 xt1 yl1) radTL borL borT (tr1, tr2, rt1, rt2) <- drawRoundedCorner renderer CornerTR rtr (p2 xt2 yr1) radTR borT borR (rb1, rb2, br1, br2) <- drawRoundedCorner renderer CornerBR rbr (p2 xb2 yr2) radBR borR borB (bl1, bl2, lb1, lb2) <- drawRoundedCorner renderer CornerBL rbl (p2 xb1 yl2) radBL borB borL drawQuad renderer lb1 lt1 lt2 lb2 borL drawQuad renderer tl1 tr1 tr2 tl2 borT drawQuad renderer rt1 rb1 rb2 rt2 borR drawQuad renderer br1 bl1 bl2 br2 borB drawRoundedCorner :: Renderer -> RectCorner -> Rect -> Point -> Maybe RadiusCorner -> Maybe BorderSide -> Maybe BorderSide -> IO (Point, Point, Point, Point) drawRoundedCorner _ _ _ center _ Nothing Nothing = return points where points = (center, center, center, center) drawRoundedCorner renderer cor bounds ocenter mrcor ms1 ms2 = do when (w1 > 0 || w2 > 0) $ do beginPath renderer if color1 == color2 then setFillColor renderer color1 else setFillLinearGradient renderer g1 g2 color1 color2 if round orad == 0 then drawRectArc renderer cor icenter w1 w2 else renderArc renderer ocenter orad deg (deg - 90) CCW renderLineTo renderer o1 if round orad > 0 && round irad > 0 then do renderLineTo renderer i1 renderArc renderer icenter irad (deg - 90) deg CW renderLineTo renderer i2 else do renderLineTo renderer icenter renderLineTo renderer o2 closePath renderer fill renderer return bordersCorners where Point ocx ocy = ocenter Point icx icy = icenter rcor = fromMaybe def mrcor s1 = fromMaybe def ms1 s2 = fromMaybe def ms2 w1 = _bsWidth s1 w2 = _bsWidth s2 color1 = _bsColor (fromJust (ms1 <|> ms2)) color2 = _bsColor (fromJust (ms2 <|> ms1)) minW = min w1 w2 orad = max 0 (_rcrWidth rcor) irad = max 0 (orad - minW) omax1 = max orad w1 omax2 = max orad w2 cxmin = min ocx icx cxmax = max ocx icx cymin = min ocy icy cymax = max ocy icy restrict (p1, p2) = (rectBoundedPoint bounds p1, rectBoundedPoint bounds p2) (deg, icenter) = case cor of CornerTL -> (270, Point (ocx - orad + w1 + irad) (ocy - orad + w2 + irad)) CornerTR -> ( 0, Point (ocx + orad - w2 - irad) (ocy - orad + w1 + irad)) CornerBR -> ( 90, Point (ocx + orad - w1 - irad) (ocy + orad - w2 - irad)) CornerBL -> (180, Point (ocx - orad + w2 + irad) (ocy + orad - w1 - irad)) (o1, o2) = restrict $ case cor of CornerTL -> (Point (ocx - omax1) cymax, Point cxmax (ocy - omax2)) CornerTR -> (Point cxmin (ocy - omax2), Point (ocx + omax1) cymax) CornerBR -> (Point (ocx + omax1) cymin, Point cxmin (ocy + omax2)) CornerBL -> (Point cxmax (ocy + omax2), Point (ocx - omax1) cymin) (i1, i2) = restrict $ case cor of CornerTL -> (Point (ocx - orad + w1) cymax, Point cxmax (ocy - orad + w2)) CornerTR -> (Point cxmin (ocy - orad + w1), Point (ocx + orad - w2) cymax) CornerBR -> (Point (ocx + orad - w1) cymin, Point cxmin (ocy + orad - w2)) CornerBL -> (Point cxmax (ocy + orad - w1), Point (ocx - orad + w2) cymin) bordersCorners | round orad == 0 = (o1, icenter, o2, icenter) | otherwise = (o1, i1, o2, i2) ocorner = Point (o1 ^. L.x) (o2 ^. L.y) icorner = Point (o2 ^. L.x) (o1 ^. L.y) (g1, g2) | cor `elem` [CornerTL, CornerBR] = cornerGradientPoints ocorner icorner | otherwise = cornerGradientPoints icorner ocorner drawRectArc :: Renderer -> RectCorner -> Point -> Double -> Double -> IO () drawRectArc renderer corner c1 pw1 pw2 = do moveTo renderer (addPoint c1 p1) renderLineTo renderer (addPoint c1 p2) renderLineTo renderer (addPoint c1 p3) where nw1 = -pw1 nw2 = -pw2 (p1, p2, p3) = case corner of CornerTL -> (Point 0 nw2, Point nw1 nw2, Point nw1 0) CornerTR -> (Point pw2 0, Point pw2 nw1, Point 0 nw1) CornerBR -> (Point 0 pw2, Point pw1 pw2, Point pw1 0) CornerBL -> (Point nw2 0, Point nw2 pw1, Point 0 pw1) drawQuad :: Renderer -> Point -> Point -> Point -> Point -> Maybe BorderSide -> IO () drawQuad renderer p1 p2 p3 p4 Nothing = pure () drawQuad renderer p1 p2 p3 p4 (Just BorderSide{..}) = when (_bsWidth > 0) $ do beginPath renderer setFillColor renderer _bsColor moveTo renderer p1 renderLineTo renderer p2 renderLineTo renderer p3 renderLineTo renderer p4 closePath renderer fill renderer cornerGradientPoints :: Point -> Point -> (Point, Point) cornerGradientPoints outer inner = (g1, g2) where Point ox oy = outer Point ix iy = inner Point mx my = midPoint outer inner (vx, vy) = (ix - ox, iy - oy) (nx, ny) = (vy, -vx) factor = 0.01 g1 = Point (mx - factor * nx) (my - factor * ny) g2 = Point (mx + factor * nx) (my + factor * ny) p2 :: Double -> Double -> Point p2 x y = Point x y radW :: Maybe RadiusCorner -> Double radW r = _rcrWidth (fromMaybe def r) fixRadius :: Rect -> Radius -> Radius fixRadius (Rect _ _ w h) (Radius tl tr bl br) = newRadius where fixC (RadiusCorner cwidth) | cwidth < min w h / 2= RadiusCorner cwidth | otherwise = RadiusCorner (min w h / 2) newRadius = Radius (fixC <$> tl) (fixC <$> tr) (fixC <$> bl) (fixC <$> br)