packages feed

layoutz 0.3.3.0 → 0.3.4.0

raw patch · 5 files changed

+144/−36 lines, 5 filesdep +text

Dependencies added: text

Files

+ CHANGELOG.md view
@@ -0,0 +1,66 @@+# Changelog++All notable changes to layoutz-hs will be documented in this file.++The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).++## [0.3.4.0] - 2026-03-31++### Added+- `renderText :: Element a => a -> Text` for rendering directly to `Data.Text.Text`.+- Unit tests for `renderText`.++### Changed+- Added `text` as an explicit dep on boot (stil technically zero dep by GHC standards)+- But easy to comment out `renderText` for porting to MicroHs++## [0.3.3.0] - 2026-03-14++### Added+- `runAppFinal`: like `runApp` but returns the final application state.+- `runAppWithFinal`: like `runAppWith` but returns the final application state.+- `runInline`: renders app inline without alt-screen, for embedded spinners/progress bars.+- New `InlineLoadingDemo` example.++## [0.3.2.0] - 2026-03-11++### Added+- `CmdExit` command for graceful app shutdown from within `appUpdate`.+- `cmdIsExit` helper to check if a command tree contains an exit.++### Fixed+- Haddock markup errors.+- `SpinnerDemo.hs` example.++## [0.3.1.0] - 2026-03-01++### Changed+- Documentation tweaks and Haddock polish.++## [0.3.0.0] - 2026-03-01++### Added+- TUI runtime (`LayoutzApp`, Elm Architecture style event loop).+- Keyboard input handling (`Key`, `readKey`).+- Commands (`Cmd`, `cmdFire`, `cmdTask`, `cmdAfterMs`).+- Subscriptions (`Sub`, `subKeyPress`, `subEveryMs`).+- `AppOptions` and `AppAlignment` for layout customization.+- Spinner animations (`SpinnerDots`, `SpinnerLine`, `SpinnerClock`, `SpinnerBounce`).+- Visualization primitives: `plotSparkline`, `plotLine`, `plotPie`, `plotBar`, `plotStackedBar`, `plotHeatmap`.+- Braille-based line and pie chart rendering.+- 256-color and RGB true-color support (`ColorFull`, `ColorTrue`).+- Text styles with `Semigroup` combining (`StyleBold <> StyleItalic`).+- `tightRow` for gapless horizontal layouts.+- `wrap` for word-boundary text wrapping.+- Ordered lists (`ol`) with nested numbering (arabic, alpha, roman).++## [0.1.0.0] - 2026-02-27++### Added+- Initial Haskell port of layoutz.+- Core DSL: `text`, `layout`, `box`, `row`, `center`, `ul`, `table`, `kv`, `tree`.+- Border styles: normal, double, thick, round, ASCII, block, dashed, dotted, half-block, markdown, custom.+- `withBorder`, `withColor`, `withStyle` combinators.+- ANSI-aware width calculation (`charWidth`, `visibleLength`).+- `hr`, `vr`, `pad`, `margin`, `chart`, `section`, `alignLeft`/`alignRight`/`alignCenter`/`justify`.+- `OverloadedStrings` support for `L`.
Layoutz.hs view
@@ -66,6 +66,7 @@   , withStyle     -- * Rendering   , render+  , renderText     -- * TUI Runtime   , LayoutzApp(..)   , Key(..)@@ -80,7 +81,9 @@   , defaultAppOptions   , AppAlignment(..)   , runApp+  , runAppFinal   , runAppWith+  , runAppWithFinal   , runInline     -- * Subscriptions   , subKeyPress@@ -89,6 +92,8 @@   ) where  import Data.List (intercalate, transpose, nub)+import qualified Data.Text+import qualified Data.Text as T import Data.Bits ((.|.)) import Data.String (IsString(..)) import Data.Char (ord, chr)@@ -114,17 +119,17 @@ -- characters, 1 for regular characters, 2 for East Asian wide and emoji. charWidth :: Char -> Int charWidth c-  | c < '\x0300' = 1  -- Fast path for ASCII and common Latin-  | c >= '\x0300' && c < '\x0370' = 0  -- Combining diacriticals-  | c >= '\x1100' && c < '\x1200' = 2  -- Hangul Jamo-  | c >= '\x2E80' && c < '\x9FFF' = 2  -- CJK-  | c >= '\xAC00' && c < '\xD7A4' = 2  -- Hangul Syllables-  | c >= '\xF900' && c < '\xFB00' = 2  -- CJK Compatibility Ideographs-  | c >= '\xFE10' && c < '\xFE20' = 2  -- Vertical forms-  | c >= '\xFE30' && c < '\xFE70' = 2  -- CJK Compatibility Forms-  | c >= '\xFF00' && c < '\xFF61' = 2  -- Fullwidth Forms-  | c >= '\xFFE0' && c < '\xFFE7' = 2  -- Fullwidth symbols-  | c >= '\x1F000' = 2  -- Emoji, symbols, supplementary ideographs+  | c < '\x0300' = 1                     -- Fast path for ASCII and common Latin+  | c >= '\x0300' && c < '\x0370' = 0    -- Combining diacriticals+  | c >= '\x1100' && c < '\x1200' = 2    -- Hangul Jamo+  | c >= '\x2E80' && c < '\x9FFF' = 2    -- CJK+  | c >= '\xAC00' && c < '\xD7A4' = 2    -- Hangul Syllables+  | c >= '\xF900' && c < '\xFB00' = 2    -- CJK Compatibility Ideographs+  | c >= '\xFE10' && c < '\xFE20' = 2    -- Vertical forms+  | c >= '\xFE30' && c < '\xFE70' = 2    -- CJK Compatibility Forms+  | c >= '\xFF00' && c < '\xFF61' = 2    -- Fullwidth Forms+  | c >= '\xFFE0' && c < '\xFFE7' = 2    -- Fullwidth symbols+  | c >= '\x1F000' = 2                   -- Emoji, symbols, supplementary ideographs   | c >= '\x20000' && c < '\x2FFFF' = 2  -- Supplementary ideographs   | c >= '\x30000' && c < '\x3FFFF' = 2  -- Tertiary ideographs   | otherwise = 1@@ -202,6 +207,9 @@ render :: Element a => a -> String render = renderElement +renderText :: Element a => a -> Data.Text.Text+renderText = T.pack . renderElement+ -- | L is the universal layout element type - a type-erased wrapper for the DSL. -- -- This allows mixing different element types in layouts while providing a common interface.@@ -384,7 +392,7 @@  -- | Border character set supporting asymmetric borders (e.g. half-block styles) data BorderChars = BorderChars-  { bcTL, bcTR, bcBL, bcBR             :: String   -- corners+  { bcTL, bcTR, bcBL, bcBR              :: String   -- corners   , bcHTop, bcHBottom                   :: String   -- horizontal (top vs bottom)   , bcVLeft, bcVRight                   :: String   -- vertical (left vs right)   , bcLeftTee, bcRightTee, bcCross      :: String   -- separator connectors@@ -1080,7 +1088,7 @@ ansiReset :: String ansiReset = "\ESC[0m" --- Sparkline ------------------------------------------------------------------+-- Sparkline --------------------------------------------------  data SparklineData = SparklineData [Double] @@ -1098,7 +1106,7 @@ plotSparkline :: [Double] -> L plotSparkline = L . SparklineData --- Line Plot (Braille) --------------------------------------------------------+-- Line Plot (Braille) --------------------------------------------------  -- | A data series for line plots: points, label, color data Series = Series [(Double, Double)] String Color@@ -1176,7 +1184,7 @@ plotLine :: Int -> Int -> [Series] -> L plotLine w h ss = L (PlotData ss w h) --- Pie Chart (Braille) --------------------------------------------------------+-- Pie Chart (Braille) --------------------------------------------------  -- | A slice of a pie chart: value, label, color data Slice = Slice Double String Color@@ -1234,7 +1242,7 @@ plotPie :: Int -> Int -> [Slice] -> L plotPie w h sl = L (PieData sl w h) --- Bar Chart (Vertical) -------------------------------------------------------+-- Bar Chart (Vertical) --------------------------------------------------------  -- | A bar item: value, label, color data BarItem = BarItem Double String Color@@ -1278,7 +1286,7 @@ plotBar :: Int -> Int -> [BarItem] -> L plotBar w h items = L (BarChartData items w h) --- Stacked Bar Chart -----------------------------------------------------------+-- Stacked Bar Chart -------------------------------------------------------  -- | A group of stacked bars: segments and group label data StackedBarGroup = StackedBarGroup [BarItem] String@@ -1359,7 +1367,7 @@ plotStackedBar :: Int -> Int -> [StackedBarGroup] -> L plotStackedBar w h groups = L (StackedBarChartData groups w h) --- Heatmap --------------------------------------------------------------------+-- Heatmap -----------------------------------------------------------  -- | Heatmap data: grid of values, row labels, column labels data HeatmapData = HeatmapData [[Double]] [String] [String]@@ -1595,14 +1603,19 @@ -- -- Press ESC, Ctrl+C, or Ctrl+D to quit the application. runApp :: LayoutzApp state msg -> IO ()-runApp = runAppWith defaultAppOptions+runApp app = runAppWithFinal defaultAppOptions app >> pure () +-- | Like 'runApp', but returns the final application state after exit.+-- Useful for interactive tweaking of state followed by further processing.+runAppFinal :: LayoutzApp state msg -> IO state+runAppFinal = runAppWithFinal defaultAppOptions+ -- | Run an app inline — no alt screen, no clearing. The app renders in-place -- below whatever is already on screen and returns when it issues 'CmdExit'. -- Useful for embedding animated progress bars or spinners in scripts. runInline :: LayoutzApp state msg -> IO ()-runInline = runAppWith defaultAppOptions-  { optClearOnStart = False, optClearOnExit = False }+runInline app = runAppWithFinal defaultAppOptions+  { optClearOnStart = False, optClearOnExit = False } app >> pure ()  -- | Run an interactive TUI application with custom options. --@@ -1610,7 +1623,16 @@ -- 'runAppWith' 'defaultAppOptions' { 'optAlignment' = 'AppAlignCenter' } myApp -- @ runAppWith :: AppOptions -> LayoutzApp state msg -> IO ()-runAppWith opts LayoutzApp{..} = do+runAppWith opts app = runAppWithFinal opts app >> pure ()++-- | Like 'runAppWith', but returns the final application state after exit.+-- Useful for interactive tweaking of state followed by further processing.+--+-- @+-- finalState <- 'runAppWithFinal' 'defaultAppOptions' { 'optAlignment' = 'AppAlignCenter' } myApp+-- @+runAppWithFinal :: AppOptions -> LayoutzApp state msg -> IO state+runAppWithFinal opts LayoutzApp{..} = do   oldBuffering <- hGetBuffering stdin   oldEcho <- hGetEcho stdin @@ -1746,9 +1768,11 @@                 Nothing -> checkExit inputLoop               Nothing -> checkExit inputLoop -  inputLoop `catch` \ex -> case ex of+  (inputLoop `catch` \ex -> case ex of     UserInterrupt -> doCleanup-    _             -> doCleanup+    _             -> doCleanup)++  readIORef stateRef  -- | Clear the screen and move cursor to top-left clearScreen :: IO ()
examples/ShowcaseApp.hs view
@@ -79,7 +79,7 @@ sceneWidth :: Int sceneWidth = 75 --- Helpers ---------------------------------------------------------------------+-- Helpers  toggleIn :: Int -> [Int] -> [Int] toggleIn x xs = if x `elem` xs then filter (/= x) xs else x : xs@@ -154,7 +154,7 @@   | abs vy < 0.05 && y < 0.1 = (0, 0)   | otherwise                 = (y, vy) --- Subscriptions ---------------------------------------------------------------+-- Subscriptions  subscriptions :: ShowcaseState -> Sub Msg subscriptions s = subBatch@@ -176,7 +176,7 @@       _            -> Nothing   ] --- View ------------------------------------------------------------------------+-- View  view :: ShowcaseState -> L view s =@@ -226,7 +226,7 @@         _ -> "  </> scenes  ESC quit"   in withStyle StyleDim $ withColor ColorBrightBlack $ text hints --- Scene 1: Physics Game -------------------------------------------------------+-- Scene 1: Physics Game  scenePhysicsGame :: ShowcaseState -> L scenePhysicsGame s =@@ -261,7 +261,7 @@         ]     ] --- Scene 2: Text Input & Lists ------------------------------------------------+-- Scene 2: Text Input & Lists  sceneTextInput :: ShowcaseState -> L sceneTextInput s =@@ -316,7 +316,7 @@         ]     ] --- Scene 3: Borders & Styles --------------------------------------------------+-- Scene 3: Borders & Styles  sceneBordersStyles :: ShowcaseState -> L sceneBordersStyles _ =@@ -347,7 +347,7 @@         ]     ] --- Scene 4: Tables -------------------------------------------------------------+-- Scene 4: Tables  sceneTables :: ShowcaseState -> L sceneTables s =@@ -376,7 +376,7 @@         ]     ] --- Scene 5: Charts & Plots ----------------------------------------------------+-- Scene 5: Charts & Plots  sceneChartsPlots :: ShowcaseState -> L sceneChartsPlots s =@@ -406,7 +406,7 @@         ]     ] --- Scene 6: Bar Charts & Sparklines -------------------------------------------+-- Scene 6: Bar Charts & Sparklines  sceneBarChartsSparklines :: ShowcaseState -> L sceneBarChartsSparklines s =@@ -446,7 +446,7 @@         ]     ] --- Scene 7: Selections & Heatmap ----------------------------------------------+-- Scene 7: Selections & Heatmap  sceneSelectionsHeatmap :: ShowcaseState -> L sceneSelectionsHeatmap s =@@ -492,7 +492,7 @@         [ plotHeatmap' 5 (HeatmapData heatData days hours) ]     ] --- App -------------------------------------------------------------------------+-- App  showcaseApp :: LayoutzApp ShowcaseState Msg showcaseApp = LayoutzApp
layoutz.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name:          layoutz-version:       0.3.3.0+version:       0.3.4.0 synopsis:      Simple, beautiful CLI output description:   Zero-dep, compositional terminal output homepage:      https://github.com/mattlianje/layoutz@@ -15,6 +15,7 @@  extra-source-files:     README.md+    CHANGELOG.md     pix/*.png  source-repository head@@ -25,6 +26,7 @@ library   exposed-modules:     Layoutz   build-depends:       base >= 4.7 && < 5+                     , text   hs-source-dirs:      .   default-language:    Haskell2010   ghc-options:         -Wall@@ -58,6 +60,7 @@   main-is:             Test.hs   build-depends:       base >= 4.7 && < 5                      , layoutz+                     , text                      , tasty >= 1.4                      , tasty-hunit >= 0.10   hs-source-dirs:      test
test/Test.hs view
@@ -4,6 +4,7 @@ import Test.Tasty.HUnit import Layoutz import Data.List (isInfixOf)+import qualified Data.Text as T import Data.IORef (newIORef, readIORef, writeIORef)  -- Helper to strip ANSI codes for testing@@ -24,6 +25,7 @@   [ basicElementTests   , visualFeatureTests   , dataVisualizationTests+  , renderTextTests   , containerTests   , layoutTests   , dimensionTests@@ -55,6 +57,19 @@          , testCase "center custom width" $       render (center' 10 $ text "Test") @?= "   Test   "+  ]++-- renderText tests+renderTextTests :: TestTree+renderTextTests = testGroup "renderText"+  [ testCase "renderText returns Text, not String" $+      renderText (text "Hello World") @?= T.pack "Hello World"++  , testCase "renderText matches render" $+      renderText (text "Test") @?= T.pack (render (text "Test"))++  , testCase "renderText on composed layout" $+      renderText (layout [text "A", text "B"]) @?= T.pack "A\nB"   ]  -- Visual feature tests