# -*- tcl -*- # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from -buildversion.txt # module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.4.tm # # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # (C) 2025 # # @@ Meta Begin # Application punk::layout 999999.0a1.0 # Meta platform tcl # Meta license MIT # @@ Meta End #EXPERIMENTAL # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ ## Requirements # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ package require Tcl 8.6- package require punk::args #experimental layout library based on Nic Barker's Clay # CLAY(CLAY_ID("parent"), { .layout = { .padding = CLAY_PADDING_ALL(8) } }) { # // Child element 1 # CLAY_TEXT(CLAY_STRING("Hello World"), CLAY_TEXT_CONFIG({ .fontSize = 16 })); # // Child element 2 with red background # CLAY(CLAY_ID("child"), { .backgroundColor = COLOR_RED }) { # // etc # } # } #Tcl translation? #CLAY ?-opt ..? ?childblock? #eg #CLAY -id -layout -rectangle { childblock } #where childblock is curly braced code block # clay -id "parent" -layout [dict create .padding [padding_all 8]] { # clay_text "Hello World" [clay_textconfig {.fontsize 16}] # clay -id "child" -config {.backgroundColor red} { # # etc # } # } #for padding on a terminal - we are limited by cell_size - which is not square. (and may vary on terminals??) # e.g punk::console::cell_size returns cell wxh # -> 10x20 # (either queried from terminal - or defaults to 10x20 if can't be queried) #so the minimum 'even' padding we can have is 2 space for horizontal, 1 line vertical #we can pad a minimum of 1 space horizontall (10 units) and a minimum of 1 line vertically (20 units) # CLAY_PADDING { } #how then should we handle .padding {16 16 8 8} - quantize by roundup to h 10 and v 20? # -> {20 20 20 20} ? # or round to nearest vertical and horizontal quanta? # -> {20 20 0 0} #either way - {16 16 16 16} goes to {20 20 20 20} - which is *close* to equal v h padding # - but not perfect visually because fonts within a cell have baseline, topline etc internal padding too? #building a ui ? #clay_begin_layout # // build UI here #set rendercommands [clay_end_layout] #some_render_command $rendercommands #eg2 (from introducing clay video https://www.youtube.com/watch?v=DYWTw19_8r4&t=2s) #something like the following? # (stopped at around the scroll point - need to work out console mouse features. punk::console::mouse_enable ...) #extracted out common layout 'sizing' for grow in both directions #set layoutExpand { # width CLAY_SIZING_GROW # heigth CLAY_SIZING GROW # } #extracted out common background panel #set contentBackgroundConfig { # colour Term-grey # frametype arc # } #proc RenderHeaderButton {} {} ;#?? #set documents [dict create\ # Squirrels "The secret life of Squirrels"\ # "Lorem Ipsum" "Orem ipsum dolor sit ..\netc blah"\ # "Vacuum instructions" "Chapter 3: Getting Started"\ # "Article 4" "Article 4"\ # "Article 5" "Article 5"\ # ] #set selectedDocumentIndex 0 #clay_begin_layout #clay -id "OuterContainer"\ # -item [clay_rectangle {colour web-red}] # -layout [clay_layout [dict create\ # layoutDirection CLAY_TOP_TO_BOTTOM\ # sizing $layoutExpand\ # padding {16 16}\ # childGap 16\ # ]]\ # { # # Child elements # clay -id "HeaderBar"\ # -item [clay_rectangle $contentBackgroundConfig]\ # -layout [clay_layout { # sizing { # height 60 # width CLAY_SIZING_GROW # } # padding {8 8} # childGap 8 # childAlignment { # y CLAY_ALIGN_Y_CENTER # } # }]\ # { # # Header buttons go here # RenderheaderButton [clay_string "File"] # RenderheaderButton [clay_string "Edit"] # #push last 3 buttons to rhs by using a layout in between to take up the middle space # CLAY -layout {sizing {width CLAY_SIZING_GROW}} {} # RenderheaderButton [clay_string "Upload"] # RenderheaderButton [clay_string "Media"] # RenderheaderButton [clay_string "Support"] # } # clay -id "LowerContent"\ # -layout [dict create sizing $layoutExpand childGap 16]\ # { # clay -id "Sidebar"\ # -item [clay_rectangle $contentBackgroundConfig]\ # -layout { # layoutDirection CLAY_TOP_TO_BOTTOM # padding {8 8} # childgap {20} # sizing { # width = 60 # height = CLAY_SIZING_GROW # }\ # }\ # { # #render dynamic data # dict for {title data} $documents { # clay -layout {padding {8 8}\ # { # #fontId, fontSize not really practical on terminal # clay_text $title [clay_text_config { # textColor cyan # }] # } # } # } # clay -id "MainContent"\ # -item [clay_rectangle $contentBackgroundConfig]\ # -scroll {vertical true}\ # -layout [dict create\ # layoutDirection = CLAY_TOP_TO_BOTTOM\ # childGap 20\ # padding {16 16 20 20}\ # sizing $layoutExpand\ # ]\ # { # set selectedTitle [lindex [dict keys $documents] $selectedDocumentIndex] # clay_text $selectedTitle [clay_text_config { # textcolour {web-white bold} # }] # clay_text [dict get $documents $selectedTitle] [clay_text_config { # textcolour {web-white} # }] # } # } # } #set rendercommands [clay_end_layout] #CLAY_SIZING_GROW #CLAY_SIZING_FIT ;#default tcl::namespace::eval punk::layout { variable PUNKARGS namespace eval argdoc { variable PUNKARGS namespace eval argdoc { #non-colour SGR codes set I "\x1b\[3m" ;# [a+ italic] set NI "\x1b\[23m" ;# [a+ noitalic] set B "\x1b\[1m" ;# [a+ bold] set N "\x1b\[22m" ;# [a+ normal] set T "\x1b\[1\;4m" ;# [a+ bold underline] set NT "\x1b\[22\;24m\x1b\[4:0m" ;# [a+ normal nounderline] } } #elementDeclaration #data structure (CLAY UIElement struct equivalent) #set eg_element [dict create\ # position {1 1}\ # size {80 24}\ # children [list]\ #] #elementConfig #set eg_item [dict create\ # type rectangle\ # color {red bold}\ # borderColor {yellow} # frametype {}\ #] # etc proc DrawRectangle {element} { set position [dict get $element position] ;#x y set size [dict get $element size] ;#width height lassign $size w h set bg [dict get $element color] set borderColor [dict get $element borderColor] set frametype [dict get $element frametype] if {$frametype eq ""} { #much faster than textblock::frame set content [textblock::block $w $h "[a+ $bg] [a]"] } else { #slow - and frame background colours will always overlap the borders so we can never get a nicely filled arc frame for example #The best bordered set content [textblock::frame -type $frametype -ansibase [a+ $bg] -ansiborder [a+ $borderColor] -width $w -height $h " "] } } proc RenderElement {element} { } #TODO #The clay_begin_layout - clay_end_layout structure suggests the main API may be better represented as an oo object maintaining state #we may need to have multiple instances running concurrently #Clay conceptually runs in 'immediate' mode - ie recalculating each full layout each time we need to render variable rendercommands proc clay_begin_layout {} { variable rendercommands set rendercommands [dict create] #...? } proc clay_end_layout {} { variable rendercommands #...? return $rendercommands } namespace eval argdoc { variable PUNKARGS lappend PUNKARGS [list { @id -id ::punk::layout::clay @cmd -name punk::layout::clay @opts -id -type string -item -type dict -layout -type dict -backgroundColor -type list -help\ "ANSI colour codes as documented in punk::ansi::a?" @values -min 0 -max 1 childscript -type script }] } #For initial proof of concept - we will use punk::args for *parsing* as well as documenting #For final version - the extra overhead of punk::args::parse may not be suitable for large numbers of #calls in 'immediate' mode - so punk::args::parse should be used on 'unhappy' paths only, #with a fast switch statement used for actual option parsing. #As performance is likely to be an issue - we should also avoid allowing 'prefixes' for arguments proc clay {args} { set argd [punk::args::parse $args withid ::punk::layout::clay] ;#temporary during concept development - todo: change to manual parsing for performance. } # } # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # Secondary API namespace # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ tcl::namespace::eval punk::layout::lib { tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase tcl::namespace::path [tcl::namespace::parent] #STEPS #Fit Sizing -> Grow Sizing -> Positions -> Draw #tree traversal #(reverse breadth first) (breadth first) # via OpenElement/CloseElement? (expand our grow containers # into any available space) proc OpenElement {element} { #pseudo... #set layoutDirection $layoutconfig.layoutDirection #if {$layoutDirection eq "CLAY_LEFT_TO_RIGHT"} { #} else { # #CLAY_TOP_TO_BOTTOM #} } proc CloseElement {element} { #element.parent.width += element.width #element.parent.height += element.height } proc SizeContainersAlongAxis {is_x_axis totalSizeToDistribute} { } proc CalculateFinalLayout {} { } } # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ #tcl::namespace::eval punk::layout::system { #} # == === === === === === === === === === === === === === === # Sample 'about' function with punk::args documentation # == === === === === === === === === === === === === === === tcl::namespace::eval punk::layout { tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase variable PUNKARGS variable PUNKARGS_aliases lappend PUNKARGS [list { @id -id "(package)punk::layout" @package -name "punk::layout" -help\ "EXPERIMENTAL - not done" }] namespace eval argdoc { #namespace for custom argument documentation proc package_name {} { return punk::layout } proc about_topics {} { #info commands results are returned in an arbitrary order (like array keys) set topic_funs [info commands [namespace current]::get_topic_*] set about_topics [list] foreach f $topic_funs { set tail [namespace tail $f] lappend about_topics [string range $tail [string length get_topic_] end] } #Adjust this function or 'default_topics' if a different order is required return [lsort $about_topics] } proc default_topics {} {return [list Description *]} # ------------------------------------------------------------- # get_topic_ functions add more to auto-include in about topics # ------------------------------------------------------------- proc get_topic_Description {} { punk::args::lib::tstr [string trim { package punk::layout An experiment in using CLAY UI style layout on the terminal INCOMPLETE... status: notes and started framework only - nothing usable. } \n] } proc get_topic_License {} { return "MIT" } proc get_topic_Version {} { return "$::punk::layout::version" } proc get_topic_Contributors {} { set authors {{Julian Noble