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 +66/−0
- Layoutz.hs +48/−24
- examples/ShowcaseApp.hs +11/−11
- layoutz.cabal +4/−1
- test/Test.hs +15/−0
+ 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