packages feed

monomer-1.5.1.0: src/Monomer/Widgets/Util/Drawing.hs

{-|
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_, void, when)
import Data.Default
import Data.Maybe
import Data.Text (Text)

import Monomer.Core
import Monomer.Graphics.Types

import qualified Monomer.Common.Lens as L
import qualified Monomer.Core.Lens as L
import qualified Monomer.Graphics.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)