You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

446 lines
17 KiB

# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-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 <val>..? ?childblock?
#eg
#CLAY -id <string> -layout <dict> -rectangle <dict> { 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 {<l> <r> <t> <b>}
#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 <julian@precisium.com.au}}
set contributors ""
foreach a $authors {
append contributors $a \n
}
if {[string index $contributors end] eq "\n"} {
set contributors [string range $contributors 0 end-1]
}
return $contributors
}
proc get_topic_concepts {} {
punk::args::lib::tstr -return string {
see Nic Barker's youtube video: How Clay's UI Layout Algorithm Works
https://www.youtube.com/watch?v=by9lQvpvMIc
}
}
# -------------------------------------------------------------
}
# we re-use the argument definition from punk::args::standard_about and override some items
set overrides [dict create]
dict set overrides @id -id "::punk::layout::about"
dict set overrides @cmd -name "punk::layout::about"
dict set overrides @cmd -help [string trim [punk::args::lib::tstr {
About punk::layout
}] \n]
dict set overrides topic -choices [list {*}[punk::layout::argdoc::about_topics] *]
dict set overrides topic -choicerestricted 1
dict set overrides topic -default [punk::layout::argdoc::default_topics] ;#if -default is present 'topic' will always appear in parsed 'values' dict
set newdef [punk::args::resolved_def -antiglobs -package_about_namespace -override $overrides ::punk::args::package::standard_about *]
lappend PUNKARGS [list $newdef]
proc about {args} {
package require punk::args
#standard_about accepts additional choices for topic - but we need to normalize any abbreviations to full topic name before passing on
set argd [punk::args::parse $args withid ::punk::layout::about]
lassign [dict values $argd] _leaders opts values _received
punk::args::package::standard_about -package_about_namespace ::punk::layout::argdoc {*}$opts {*}[dict get $values topic]
}
}
# end of sample 'about' function
# == === === === === === === === === === === === === === ===
# -----------------------------------------------------------------------------
# register namespace(s) to have PUNKARGS,PUNKARGS_aliases variables checked
# -----------------------------------------------------------------------------
# variable PUNKARGS
# variable PUNKARGS_aliases
namespace eval ::punk::args::register {
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::layout
}
# -----------------------------------------------------------------------------
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::layout [tcl::namespace::eval punk::layout {
variable pkg punk::layout
variable version
set version 999999.0a1.0
}]
return