Skip to content

Commit

Permalink
Support centering content with auto margins (#164)
Browse files Browse the repository at this point in the history
See #38.

Configuration is done through the existing `margins` setting.

To vertically center content, use `top: auto`. To horizontally center content,
use both `left: auto` and `right: auto`.  For example:

```markdown
---
title: Centered presentation
author: John Doe
patat:
    margins:
        left: auto
        right: auto
        top: auto
...

Hello world
```

Setting `wrap: true` is recommended when vertically centering content if there
are any lines that are too wide for the terminal.
  • Loading branch information
jaspervdj authored Feb 12, 2024
1 parent bf0ef67 commit 1032678
Show file tree
Hide file tree
Showing 19 changed files with 385 additions and 98 deletions.
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Changelog

## unreleased

* Support centering content with auto margins (#164)

Configuration is done through the existing `margins` setting.

To vertically center content, use `top: auto`. To horizontally center
content, use both `left: auto` and `right: auto`. For example:

```markdown
---
title: Centered presentation
author: John Doe
patat:
margins:
left: auto
right: auto
top: auto
...

Hello world
```

Setting `wrap: true` is recommended when vertically centering content if
there are any lines that are too wide for the terminal.

## 0.10.2.0 (2023-11-25)

* Add eval.wrap option
Expand Down
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ feature-rich presentation tool that runs in the terminal.
- [Transition effects](#transitions).
- Supports [smart slide splitting](#input-format).
- [Auto advancing](#auto-advancing) with configurable delay.
- Optionally [re-wrapping](#line-wrapping) text to terminal width with proper
indentation.
- [Centering](#centering) and [re-wrapping](#line-wrapping) text to terminal
width with proper indentation.
- [Theming](#theming) support including 24-bit RGB.
- Hihgly portable as it only requires an ANSI terminal as opposed to
something like `ncurses`.
Expand Down Expand Up @@ -333,6 +333,28 @@ margin.
By default, the `left` and `right` margin are set to 0, and the `top` margin is
set to 1.

#### Centering

To vertically center content, use `top: auto`. To horizontally center content,
use both `left: auto` and `right: auto`. For example:

```markdown
---
title: Centered presentation
author: John Doe
patat:
margins:
left: auto
right: auto
top: auto
...
Hello world
```

Setting `wrap: true` is recommended when vertically centering content if there
are any lines that are too wide for the terminal.

### Auto advancing

By setting `autoAdvanceDelay` to a number of seconds, `patat` will automatically
Expand Down
108 changes: 63 additions & 45 deletions lib/Patat/Presentation/Display.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Patat.Presentation.Display.CodeBlock
import Patat.Presentation.Display.Internal
import Patat.Presentation.Display.Table
import Patat.Presentation.Internal
import Patat.Presentation.Settings
import Patat.PrettyPrint ((<$$>), (<+>))
import qualified Patat.PrettyPrint as PP
import Patat.Size
Expand All @@ -41,26 +42,28 @@ data Display = DisplayDoc PP.Doc | DisplayImage FilePath deriving (Show)
-- | Display something within the presentation borders that draw the title and
-- the active slide number and so on.
displayWithBorders
:: Size -> Presentation -> (Size -> DisplaySettings -> PP.Doc) -> PP.Doc
:: Size -> Presentation -> (DisplaySettings -> PP.Doc) -> PP.Doc
displayWithBorders (Size rows columns) pres@Presentation {..} f =
(if null title
then mempty
else
let titleRemainder = columns - titleWidth - titleOffset
wrappedTitle = PP.spaces titleOffset <> PP.string title <> PP.spaces titleRemainder in
borders wrappedTitle <> PP.hardline) <>
mconcat (replicate topMargin PP.hardline) <>
formatWith settings (f canvasSize ds) <> PP.hardline <>
f ds <> PP.hardline <>
PP.goToLine (rows - 2) <>
borders (PP.space <> PP.string author <> middleSpaces <> PP.string active <> PP.space) <>
PP.hardline
where
-- Get terminal width/title
(sidx, _) = pActiveFragment
settings = (activeSettings pres) {psColumns = Just $ A.FlexibleNum columns}
ds = DisplaySettings
{ dsTheme = fromMaybe Theme.defaultTheme (psTheme settings)
, dsSyntaxMap = pSyntaxMap
(sidx, _) = pActiveFragment
settings = activeSettings pres
ds = DisplaySettings
{ dsSize = canvasSize
, dsWrap = fromMaybe False $ psWrap settings
, dsMargins = margins settings
, dsTheme = fromMaybe Theme.defaultTheme (psTheme settings)
, dsSyntaxMap = pSyntaxMap
}

-- Compute title.
Expand All @@ -82,8 +85,7 @@ displayWithBorders (Size rows columns) pres@Presentation {..} f =
borders = themed ds themeBorders

-- Room left for content
topMargin = mTop $ margins settings
canvasSize = Size (rows - 2 - topMargin) columns
canvasSize = Size (rows - 3) columns

-- Compute footer.
active
Expand All @@ -105,18 +107,12 @@ displayPresentation size pres@Presentation {..} =
, Just image <- onlyImage fragment ->
DisplayImage $ T.unpack image
Just (ActiveContent fragment) -> DisplayDoc $
displayWithBorders size pres $ \_canvasSize theme ->
displayWithBorders size pres $ \theme ->
prettyFragment theme fragment
Just (ActiveTitle block) -> DisplayDoc $
displayWithBorders size pres $ \canvasSize theme ->
let pblock = prettyBlock theme block
(prows, pcols) = PP.dimensions pblock
Margins {..} = margins (activeSettings pres)
offsetRow = (sRows canvasSize `div` 2) - (prows `div` 2)
offsetCol = ((sCols canvasSize - mLeft - mRight) `div` 2) - (pcols `div` 2)
spaces = PP.Indentation offsetCol mempty in
mconcat (replicate (offsetRow - 3) PP.hardline) <$$>
PP.indent spaces spaces pblock
displayWithBorders size pres $ \ds ->
let auto = Margins {mTop = Auto, mRight = Auto, mLeft = Auto} in
prettyFragment ds {dsMargins = auto} $ Fragment [block]

where
-- Check if the fragment consists of "just a single image". Discard
Expand All @@ -132,11 +128,10 @@ displayPresentation size pres@Presentation {..} =
-- | Displays an error in the place of the presentation. This is useful if we
-- want to display an error but keep the presentation running.
displayPresentationError :: Size -> Presentation -> String -> PP.Doc
displayPresentationError size pres err =
displayWithBorders size pres $ \_ ds ->
themed ds themeStrong "Error occurred in the presentation:" <$$>
"" <$$>
(PP.string err)
displayPresentationError size pres err = displayWithBorders size pres $ \ds ->
themed ds themeStrong "Error occurred in the presentation:" <$$>
"" <$$>
(PP.string err)


--------------------------------------------------------------------------------
Expand All @@ -149,11 +144,10 @@ dumpPresentation pres@Presentation {..} =
dumpSlide :: Int -> [PP.Doc]
dumpSlide i = do
slide <- maybeToList $ getSlide i pres
map (formatWith (getSettings i pres)) $
dumpComment slide <> L.intercalate ["{fragment}"]
[ dumpFragment (i, j)
| j <- [0 .. numFragments slide - 1]
]
dumpComment slide <> L.intercalate ["{fragment}"]
[ dumpFragment (i, j)
| j <- [0 .. numFragments slide - 1]
]

dumpComment :: Slide -> [PP.Doc]
dumpComment slide = do
Expand All @@ -177,25 +171,49 @@ dumpPresentation pres@Presentation {..} =
Size {..}


--------------------------------------------------------------------------------
formatWith :: PresentationSettings -> PP.Doc -> PP.Doc
formatWith ps = wrap . indent
where
Margins {..} = margins ps
wrap = case (psWrap ps, psColumns ps) of
(Just True, Just (A.FlexibleNum col)) -> PP.wrapAt (Just $ col - mRight)
_ -> id
spaces = PP.Indentation mLeft mempty
indent = PP.indent spaces spaces


--------------------------------------------------------------------------------
prettyFragment :: DisplaySettings -> Fragment -> PP.Doc
prettyFragment ds (Fragment blocks) =
prettyBlocks ds blocks <>
prettyFragment ds (Fragment blocks) = vertical $
PP.vcat (map (horizontal . prettyBlock ds) blocks) <>
case prettyReferences ds blocks of
[] -> mempty
refs -> PP.hardline <> PP.vcat refs
refs -> PP.hardline <> PP.vcat (map horizontal refs)
where
Size rows columns = dsSize ds
Margins {..} = dsMargins ds

vertical doc0 =
mconcat (replicate top PP.hardline) <> doc0
where
top = case mTop of
Auto -> let (r, _) = PP.dimensions doc0 in (rows - r) `div` 2
NotAuto x -> x

horizontal = horizontalIndent . horizontalWrap

horizontalIndent doc0 = PP.indent indentation indentation doc1
where
doc1 = case (mLeft, mRight) of
(Auto, Auto) -> PP.deindent doc0
_ -> doc0
(_, dcols) = PP.dimensions doc1
left = case mLeft of
NotAuto x -> x
Auto -> case mRight of
NotAuto _ -> 0
Auto -> (columns - dcols) `div` 2
indentation = PP.Indentation left mempty

horizontalWrap doc0
| dsWrap ds = PP.wrapAt (Just $ columns - right - left) doc0
| otherwise = doc0
where
right = case mRight of
Auto -> 0
NotAuto x -> x
left = case mLeft of
Auto -> 0
NotAuto x -> x


--------------------------------------------------------------------------------
Expand Down
13 changes: 9 additions & 4 deletions lib/Patat/Presentation/Display/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ module Patat.Presentation.Display.Internal


--------------------------------------------------------------------------------
import qualified Patat.PrettyPrint as PP
import qualified Patat.Theme as Theme
import qualified Skylighting as Skylighting
import Patat.Presentation.Internal (Margins)
import qualified Patat.PrettyPrint as PP
import Patat.Size (Size)
import qualified Patat.Theme as Theme
import qualified Skylighting as Skylighting


--------------------------------------------------------------------------------
data DisplaySettings = DisplaySettings
{ dsTheme :: !Theme.Theme
{ dsSize :: !Size
, dsWrap :: !Bool
, dsMargins :: !Margins
, dsTheme :: !Theme.Theme
, dsSyntaxMap :: !Skylighting.SyntaxMap
}

Expand Down
12 changes: 7 additions & 5 deletions lib/Patat/Presentation/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ data Presentation = Presentation

--------------------------------------------------------------------------------
data Margins = Margins
{ mTop :: Int
, mLeft :: Int
, mRight :: Int
{ mTop :: AutoOr Int
, mLeft :: AutoOr Int
, mRight :: AutoOr Int
} deriving (Show)


Expand All @@ -92,8 +92,10 @@ margins ps = Margins
, mTop = get 1 msTop
}
where
get def f = fromMaybe def . fmap A.unFlexibleNum $ psMargins ps >>= f

get def f = case psMargins ps >>= f of
Just Auto -> Auto
Nothing -> NotAuto def
Just (NotAuto fn) -> NotAuto $ A.unFlexibleNum fn

--------------------------------------------------------------------------------
data Slide = Slide
Expand Down
17 changes: 14 additions & 3 deletions lib/Patat/Presentation/Settings.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Patat.Presentation.Settings
( PresentationSettings (..)
, defaultPresentationSettings

, AutoOr (..)
, MarginSettings (..)

, ExtensionList (..)
Expand Down Expand Up @@ -100,11 +101,21 @@ defaultPresentationSettings = mempty
}


--------------------------------------------------------------------------------
data AutoOr a = Auto | NotAuto a deriving (Show)


--------------------------------------------------------------------------------
instance A.FromJSON a => A.FromJSON (AutoOr a) where
parseJSON (A.String "auto") = pure Auto
parseJSON val = NotAuto <$> A.parseJSON val


--------------------------------------------------------------------------------
data MarginSettings = MarginSettings
{ msTop :: !(Maybe (A.FlexibleNum Int))
, msLeft :: !(Maybe (A.FlexibleNum Int))
, msRight :: !(Maybe (A.FlexibleNum Int))
{ msTop :: !(Maybe (AutoOr (A.FlexibleNum Int)))
, msLeft :: !(Maybe (AutoOr (A.FlexibleNum Int)))
, msRight :: !(Maybe (AutoOr (A.FlexibleNum Int)))
} deriving (Show)


Expand Down
24 changes: 24 additions & 0 deletions lib/Patat/PrettyPrint.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module Patat.PrettyPrint

, Indentation (..)
, indent
, deindent

, ansi

Expand Down Expand Up @@ -99,6 +100,29 @@ indent firstLineDoc otherLinesDoc doc = mkDoc $ Indent
}


--------------------------------------------------------------------------------
-- | Only strips leading spaces
deindent :: Doc -> Doc
deindent = Doc . concatMap go . unDoc
where
go :: DocE Doc -> [DocE Doc]
go doc@(Indent {..})
| fs0 <= 0 && os0 <= 0 = [doc]
| fs1 == 0 && os1 == 0 && L.null fc && L.null oc =
concatMap go $ unDoc indentDoc
| otherwise = pure $ Indent
{ indentFirstLine = Indentation fs1 fc
, indentOtherLines = Indentation os1 oc
, indentDoc = indentDoc
}
where
Indentation fs0 fc = indentFirstLine
Indentation os0 oc = indentOtherLines
fs1 = fs0 - min fs0 os0
os1 = os0 - min fs0 os0
go doc = [doc]


--------------------------------------------------------------------------------
ansi :: [Ansi.SGR] -> Doc -> Doc
ansi codes = mkDoc . Ansi (codes ++)
Expand Down
Loading

0 comments on commit 1032678

Please sign in to comment.