theme_engine
 // This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © kaigouthro

//@version=5
//@description Theme Builder and Structure for live generative themes
library("theme_engine",true)


import kaigouthro/hsvColor/15 as h
//=======================================================
//#region     Type declarations
//=======================================================

// @type Settings for a theme
// @field h1  (float) Hue #1 for Template
// @field h2  (float) Hue #2 for Template
// @field h3  (float) Hue #3 for Template
// @field h4  (float) Hue #4 for Template
// @field sat (float) Saturation of theme
// @field lum (float) Luminosity (light/dark)
// @field vib (float) Vibrance (Contrast)
// @field r   (float) Hue of Red
// @field o   (float) Hue of Orange
// @field y   (float) Hue of Yellow
// @field g   (float) Hue of Green
// @field a   (float) Hue of Aqua
// @field b   (float) Hue of Blue
// @field i   (float) Hue of Indigo
// @field v   (float) Hue of Violet
// @field satvals (settings) Array for use if desired to customize Saturation per color
// @field lumvals (settings) Array for use if desired to customize Luminancce per color
export type settings
    float h1       = 0
    float h2       = 0
    float h3       = 0
    float h4       = 0
    float sat      = 1
    float lum      = 1  
    float vib      = 1      
    float r        = 0
    float o        = 30
    float y        = 60
    float g        = 120
    float a        = 210
    float b        = 240
    float i        = 270
    float v        = 330
    settings satvals
    settings lumvals

// @type  Modifiers Item for Use if desired
// @field val (float) 
// @field size (float) 
// @field depth (float) 
// @field hue (float) 
// @field sat (float) 
// @field alpha (float) 
// @field mix (float) 
// @field emit (float) 
// @field ch (float) 
// @field step (int) 
// @field dist (int) 
// @field holds (mods) 
// @field isin (mods) 
// @field track (color) 
export type mods
    float val      = 1
    float size     = 1
    float depth    = 1
    float hue      = 0
    float sat      = 1
    float alpha    = 1
    float mix      = 1
    float emit     = 0
    float ch       = 0
    int   step     = 1
    int   dist     = 1
    mods  holds
    mods  isin
    color track


// @type  Light/Dark/Custom Theme Varients
// @field bg (color) Bacckground Color
// @field fg (color) Foreground Color 
// @field accent (color) Accccent Color
// @field secondary (color) Secondary Color
// @field txt (color) Text Color 
export type varient
    color bg        = na
    color fg        = na
    color accent    = na
    color secondary = na
    color txt       = na


// @type  Theme Object
// @field name   (string)   Theme name
// @field dark   (varient)  Theme dark  Varient
// @field light  (varient)  Theme light Varient
// @field red    (color)    Color for red
// @field orange (color)    Color for orange
// @field yellow (color)    Color for yellow
// @field green  (color)    Color for green
// @field aqua   (color)    Color for aqua
// @field blue   (color)    Color for blue
// @field purple (color)    Color for purple
// @field pink   (color)    Color for pink
// @field tweaks (mods)     Modifiers UDT to use for adjusters
export type theme
    string  name   = ''
    varient dark   = na
    varient light  = na
    color   red    = color.red 
    color   orange = color.orange 
    color   yellow = color.yellow
    color   green  = color.green
    color   aqua   = color.aqua
    color   blue   = color.blue
    color   purple = color.purple
    color   pink   = color.fuchsia
    mods    tweaks


// @type  Multiple theme Container
// @field names  (string[]) Names of themes  
// @field themes (theme[])  Theme Items
// @type 
export type themedict
    string[] names
    theme[]  themes

//#endregion ============================================ 
//#region         Functions for themes
//=======================================================

//@function New theme object.
//@param _name (string) opptional name
//@returns a VAR theme (holds it's vals when updated if not overwritten)
export init(string _name = 'New Theme') => 
    var _red      = color.red     
    var _orange   = color.orange  
    var _yellow   = color.yellow  
    var _green    = color.green   
    var _aqua     = color.aqua    
    var _blue     = color.blue    
    var _purple   = color.purple  
    var _fuchsia  = color.fuchsia 
    var dark_bg         = color(na),  var dark_accent      = color(na)
    var dark_fg         = color(na),  var dark_comment     = color(na)
    var light_bg        = color(na),  var light_accent     = color(na)
    var light_fg        = color(na),  var light_comment    = color(na)        
    var light_ttl       = color(na),  var dark_ttl         = color(na)        
    var dark            = varient.new(dark_bg  ,dark_fg, dark_accent, dark_comment,dark_ttl )
    var light           = varient.new(light_bg,light_fg,light_accent,light_comment,light_ttl)
    var theme = theme.new(_name , 
          dark     = dark       ,
          light    = light      ,
          red      = _red       , 
          orange   = _orange    ,
          yellow   = _yellow    ,
          green    = _green     ,
          aqua     = _aqua      ,
          blue     = _blue      ,
          purple   = _purple    ,
          pink     = _fuchsia   ,
          tweaks   = mods.new())
    theme



// @function helper to do brihgtness of globals
brightness(n,c)=> math.pow(1 + c/2, 10 * (n/2) - 10)

// @function Create light/dark theme globals
// @param theme     (theme) Theme to add theses to
// @param _h1       (float) Hue #1 for Template
// @param _h2       (float) Hue #2 for Template
// @param _h3       (float) Hue #3 for Template
// @param _h4       (float) Hue #4 for Template
// @param _s        (float) Saturation of theme
// @param _val      (float) Luminosity (light/dark)
// @param _contrast (float) Contrast to apply
// @returns Theme wwith adjusted colors
export globals(theme theme,float _h1,float _h2,float _h3,float _h4,float _s,float _val,float _contrast)=>
    _v = math.max(0,math.min(1,_val       ))
    _c = math.max(0,math.min(1,_contrast  ))
    theme.dark.bg        := h.hsv(_h1,_s/3  , brightness(_v , 1 -_c /10   ) , 1  ) , theme.light.bg        := h.hsv(_h1,_s/13  , brightness(_v ,    _c /10  ) ,1)
    theme.dark.accent    := h.hsv(_h2,_s/3  , brightness(_v , 1 -_c / 3   ) , 1  ) , theme.light.accent    := h.hsv(_h2,_s/5   , brightness(_v ,    _c / 3  ) ,1)
    theme.dark.secondary := h.hsv(_h3,_s/5  , brightness(_v , _c    / 3   ) , 1  ) , theme.light.secondary := h.hsv(_h3,_s/3   , brightness(_v , 1 -_c / 3  ) ,1)
    theme.dark.fg        := h.hsv(_h4,_s/13 , brightness(_v , _c    /10   ) , 1  ) , theme.light.fg        := h.hsv(_h4,_s/3   , brightness(_v , 1 -_c /10  ) ,1)
    theme.dark.txt       := h.hsv(0  ,    0 , 0.9                           , 1  ) , theme.light.txt       := h.hsv(  0,    0  , _v + _c                      ,1)
    theme 

// @function Apply a Settings object to a theme
// @param theme Theme object to apply settings to.
// @param setting settings to apply
// @returns theme
export setConfig (theme theme , settings setting) =>
    _sat           = setting.sat
    _bright        = setting.vib
    bg             = #000000
    acc            = #3f3f3f
    cmm            = #7f7f7f
    fg             = #bfbfbf
    ttl            = #ffffff
    theme.red     :=  h.hsv       ( setting.r ,_sat, _bright , 1)
    theme.orange  :=  h.hsv       ( setting.o ,_sat, _bright , 1)
    theme.yellow  :=  h.hsv       ( setting.y ,_sat, _bright , 1)
    theme.green   :=  h.hsv       ( setting.g ,_sat, _bright , 1)
    theme.aqua    :=  h.hsv       ( setting.a ,_sat, _bright , 1)
    theme.blue    :=  h.hsv       ( setting.b ,_sat, _bright , 1)
    theme.purple  :=  h.hsv       ( setting.i ,_sat, _bright , 1)
    theme.pink    :=  h.hsv       ( setting.v ,_sat, _bright , 1)
    theme.dark    := varient.new  ( bg        ,acc , cmm     ,fg)
    theme.light   := varient.new  ( fg        ,cmm , acc     ,bg)
    globals(theme, setting.h1 , setting.h2, setting.h3 , setting.h4,setting.sat , setting.lum, setting.vib )




//#endregion ============================================ 




////            demo


// //demo testing inputs for settings obj
// _count    = input(10,'demo generate count')
// g1        = input(1, 'Global hue 1', inline='gcol',                  group='Globals')
// g2        = input(1, 'Global hue 2', inline='gcol',                  group='Globals')
// g3        = input(1, 'Global hue 3', inline='gcol',                  group='Globals')
// g4        = input(1, 'Global hue 4', inline='gcol',                  group='Globals')
// gs        = input.float(1,'sat'        ,step=0.1,  inline='hsv', group='Globals')
// gv        = input.float(1,'bright'     ,step=0.1,  inline='hsv', group='Globals')
// gc        = input.float(1,'contrast'   ,step=0.1,  inline='hsv', group='Globals')
// _rBase    = input( 0   ,'r',inline='sh',group='per-color') 
// _oBase    = input( 30  ,'o',inline='sh',group='per-color') 
// _yBase    = input( 60  ,'y',inline='sh',group='per-color') 
// _gBase    = input( 120 ,'g',inline='sh',group='per-color') 
// _aBase    = input( 210 ,'a',inline='sh',group='per-color') 
// _bBase    = input( 240 ,'b',inline='sh',group='per-color') 
// _iBase    = input( 270 ,'i',inline='sh',group='per-color') 
// _vBase    = input( 330 ,'v',inline='sh',group='per-color') 



// // helpers for demo
// addSomeRandom() => math.random(-5,5)

// spc(x)=> 
//     s = ''
//     for i = 0 to x
//         s+= font.space(6)

// // create a multitheme dictionary to hold more than one theme
// var dict   = themedict.new(array.new<string>(_count,''),array.new<theme>(_count))

// // local function just to generate multiple themes
// addshortcut(i,_count)=>
//     array.set(dict.names ,i,str.tostring(i))
//     var _mult = 0.
//     _mult :=  i/_count
//     // theme = theme.copy(array.get(dict.themes,i))
//     var theme = init()
//     theme := setConfig(theme,
//           settings.new  (
//           _mult * 360 + g1  ,
//           _mult * 360 + g2  ,
//           _mult * 360 + g3  ,
//           _mult * 360 + g4  ,
//           _mult * gs  ,
//           _mult + gv  ,
//           _mult + gc  ,          
//           addSomeRandom() + _rBase ,          
//           addSomeRandom() + _oBase ,          
//           addSomeRandom() + _yBase ,          
//           addSomeRandom() + _gBase ,          
//           addSomeRandom() + _aBase ,          
//           addSomeRandom() + _bBase ,          
//           addSomeRandom() + _iBase ,          
//           addSomeRandom() + _vBase ))
    
//     theme

// // for display purposes, local items
// import kaigouthro/matrixautotable/14 as mtb
// import kaigouthro/calc/5
// import kaigouthro/font/4

// // just to randomly shift the color values 
// shift(c,r)=>
//     [h,s,v,a]  = h.rgbhsv(c)
//     h+= r
//     s+= r/50
//     v-= r/50
//     h.hsv(h,s,v,a)

// testshift(theme)=>
//     theme.red     := shift(theme.red      ,addSomeRandom())
//     theme.orange  := shift(theme.orange   ,addSomeRandom())
//     theme.yellow  := shift(theme.yellow   ,addSomeRandom())
//     theme.green   := shift(theme.green    ,addSomeRandom())
//     theme.aqua    := shift(theme.aqua     ,addSomeRandom())
//     theme.blue    := shift(theme.blue     ,addSomeRandom())
//     theme.purple  := shift(theme.purple   ,addSomeRandom())
//     theme.pink    := shift(theme.pink     ,addSomeRandom())
//     theme


// //////////////////////////////////////////////////////////////////////////////////////////
// /////////////////
// /////////////////           HERE
// /////////////////

// //  when creating and adding to an array, this is a pine limitation..  

// if barstate.isfirst

//     for i = 0 to array.size(dict.themes) -1       
//         switch input.int(0,'three ways',0,2)
            
//             0  =>  // creates a new theme for each, then copies it, places copy
//                 theme = addshortcut(i,array.size(dict.themes))
//                 array.set(dict.themes,i, theme.copy(theme))
            
//             1  =>  // only first  makes it
//                 var theme = addshortcut(i,array.size(dict.themes))
//                 array.set(dict.themes,i, theme)
            
//             2  =>  // only last makes it
//                 theme = addshortcut(i,array.size(dict.themes))
//                 array.set(dict.themes,i, theme)
            

// /////////////////
// /////////////////           HERE
// /////////////////
// //////////////////////////////////////////////////////////////////////////////////////////

// if barstate.islast

//     _out   = matrix.new<color>    ( 0,16                 )   
//     _st    = array.copy           ( dict.names           )                  
//     _fm    = matrix.new  <float>  ( _count,16,float(na)  )                  
//     _in    = matrix.new<color>    ( _count,16            )   
//     _txin  = matrix.new<color>    ( _count,16            )   
//     _smtx  = matrix.new  <string> ( _count,16            )   
//     _tmtx  = matrix.new  <string> ( _count,16            )   


//     for [_n,theme] in dict.themes
        
//         // adjuust colors without creating new theme
//         testshift(array.get(dict.themes,_n))
        
        
//         matrix.fill(_tmtx,theme.name,_n,_n+1,0,16)
//         matrix.set(_in , _n , 00 , theme.dark.bg        )              
//         matrix.set(_in , _n , 01 , theme.dark.accent    )                  
//         matrix.set(_in , _n , 02 , theme.dark.secondary   )                  
//         matrix.set(_in , _n , 03 , theme.dark.fg        )              
//         matrix.set(_in , _n , 04 , theme.light.bg       )              
//         matrix.set(_in , _n , 05 , theme.light.accent   )                  
//         matrix.set(_in , _n , 06 , theme.light.secondary  )                 
//         matrix.set(_in , _n , 07 , theme.light.fg       )              
//         matrix.set(_in , _n , 08 , theme.red            )     
//         matrix.set(_in , _n , 09 , theme.orange         )     
//         matrix.set(_in , _n , 10 , theme.yellow         )     
//         matrix.set(_in , _n , 11 , theme.green          )     
//         matrix.set(_in , _n , 12 , theme.aqua           )     
//         matrix.set(_in , _n , 13 , theme.blue           )     
//         matrix.set(_in , _n , 14 , theme.purple         )     
//         matrix.set(_in , _n , 15 , theme.pink           )     
//         //testt that updating master updates theme
            
//         matrix.set(_txin,_n,00, theme.dark.fg                        )              
//         matrix.set(_txin,_n,01, theme.dark.txt                       )  
//         matrix.set(_txin,_n,02, theme.dark.txt                       ) 
//         matrix.set(_txin,_n,03, theme.dark.bg                        )               
//         matrix.set(_txin,_n,04, theme.light.fg                       )              
//         matrix.set(_txin,_n,05, theme.light.txt                      )
//         matrix.set(_txin,_n,06, theme.light.txt                      )
//         matrix.set(_txin,_n,07, theme.light.bg                       )              
//         matrix.set(_txin,_n,08, h.tripswitch(theme.red           , 0.8, #ffffff,#000000   )) 
//         matrix.set(_txin,_n,09, h.tripswitch(theme.orange        , 0.8, #ffffff,#000000   )) 
//         matrix.set(_txin,_n,10, h.tripswitch(theme.yellow        , 0.8, #ffffff,#000000   )) 
//         matrix.set(_txin,_n,11, h.tripswitch(theme.green         , 0.8, #ffffff,#000000   )) 
//         matrix.set(_txin,_n,12, h.tripswitch(theme.aqua          , 0.8, #ffffff,#000000   )) 
//         matrix.set(_txin,_n,13, h.tripswitch(theme.blue          , 0.8, #ffffff,#000000   )) 
//         matrix.set(_txin,_n,14, h.tripswitch(theme.purple        , 0.8, #ffffff,#000000   )) 
//         matrix.set(_txin,_n,15, h.tripswitch(theme.pink          , 0.8, #ffffff,#000000   )) 
        
//         if input.bool(false,'show text')
            
//             matrix.set(_smtx,_n,00, font.uni((_n == _count-1?'dark bg \n text - fg'  : 'text') ,15))
//             matrix.set(_smtx,_n,01, font.uni((_n == _count-1?'dark accent \n text - txt' : 'text') ,15))
//             matrix.set(_smtx,_n,02, font.uni((_n == _count-1?'dark secondary \n text - txt' : 'text') ,15))
//             matrix.set(_smtx,_n,03, font.uni((_n == _count-1?'dark fg \n text - bg'  : 'text') ,15))
//             matrix.set(_smtx,_n,04, font.uni((_n == _count-1?'light bg \n text - fg'  : 'text') ,15))
//             matrix.set(_smtx,_n,05, font.uni((_n == _count-1?'light accent  \n text - txt' : 'text') ,15))
//             matrix.set(_smtx,_n,06, font.uni((_n == _count-1?'light secondary \n text - txt' : 'text') ,15))
//             matrix.set(_smtx,_n,07, font.uni((_n == _count-1?'light fg \n text - bg'  : 'text') ,15))
//             matrix.set(_smtx,_n,08, font.uni(_n==_count-1?'red' : ''                                 ,15))
//             matrix.set(_smtx,_n,09, font.uni(_n==_count-1?'orange' : ''                              ,15))
//             matrix.set(_smtx,_n,10, font.uni(_n==_count-1?'yellow' : ''                              ,15))
//             matrix.set(_smtx,_n,11, font.uni(_n==_count-1?'green' : ''                               ,15))
//             matrix.set(_smtx,_n,12, font.uni(_n==_count-1?'aqua' : ''                                ,15))
//             matrix.set(_smtx,_n,13, font.uni(_n==_count-1?'blue' : ''                                ,15))
//             matrix.set(_smtx,_n,14, font.uni(_n==_count-1?'purple' : ''                              ,15))
//             matrix.set(_smtx,_n,15, font.uni(_n==_count-1?'pink' : ''                                ,15))

//     // show the table
//     if input(false,'Rotate table 90 degrees')
//         _fm := matrix.transpose(_fm)
//         _txin := matrix.transpose(_txin)
//         _smtx := matrix.transpose(_smtx)
//         _in   := matrix.transpose(_in)
//     _tbl = mtb.matrixtable(_fm, _smtx,_in,_txin,input(2,'textsize'), _fit=true)
//     mtb.border(_tbl,#00000000,input(true,'Borders')?1:0)
     
