theme.monkey2 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. Namespace mojo.app
  2. Class Theme
  3. Field ThemeChanged:Void()
  4. Method New()
  5. _defaultStyle=New Style
  6. _defaultStyle.Font=App.DefaultFont
  7. End
  8. Property Scale:Vec2f()
  9. Return _themeScale
  10. Setter( scale:Vec2f )
  11. If scale=_themeScale Return
  12. _themeScale=scale
  13. Reload()
  14. End
  15. Property DefaultStyle:Style()
  16. Return _defaultStyle
  17. End
  18. #rem monkeydoc Gets a font from the theme.
  19. If no font named `name` is found, [[App.DefaultFont]] is returned.
  20. #end
  21. Method GetFont:Font( name:String )
  22. Local font:=_fonts[name]
  23. If font Return font
  24. Return App.DefaultFont
  25. End
  26. #rem monkeydoc Gets a color from the theme.
  27. If no color named `name` is found, [[Color.Grey]] is returned.
  28. #end
  29. Method GetColor:Color( name:String )
  30. If _colors.Contains( name ) Return _colors[name]
  31. Return Color.Grey
  32. End
  33. #rem monkeydoc Gets a style from the theme.
  34. If no style named `name` is found, the [[DefaultStyle]] for the theme is returned.
  35. #end
  36. Method GetStyle:Style( name:String )
  37. Local style:=_styles[name]
  38. If style Return style
  39. Return _defaultStyle
  40. End
  41. #rem monkeydoc Loads a font from a file.
  42. If `file` is an absolute path, this method is effectively the same as calling [[Font.Load]].
  43. If `file` is not an absolute path, the font is searched for in the following locations:
  44. * The theme's directory.
  45. * The asset::fonts/ directory.
  46. #end
  47. Method OpenFont:Font( path:String,size:Float )
  48. size*=_themeScale.y
  49. Local font:Font
  50. If Not ExtractRootDir( path ) font=_res.OpenFont( "theme::"+path,size )
  51. If Not font font=_res.OpenFont( path,size )
  52. If Not font Return App.DefaultFont
  53. Return font
  54. End
  55. Method OpenImage:Image( path:String )
  56. Local image:Image
  57. If Not ExtractRootDir( path ) image=_res.OpenImage( "theme::"+path )
  58. If Not image image=_res.OpenImage( path )
  59. If Not image Return null
  60. image.Scale=_themeScale
  61. Return image
  62. End
  63. #rem monkeydoc Loads an array of icon images from a file.
  64. Loads an array of icons from an image file.
  65. The icons should be square, and laid out in a horizontal 'strip' within the image file.
  66. The width and height of each image is taken from the height of the image file.
  67. The number of icons loaded is the width of the image file divided by its height.
  68. #end
  69. Method OpenIcons:Image[]( path:String )
  70. Local slug:="Icons:"+StripDir( StripExt( path ) )
  71. Local icons:=Cast<Icons>( _res.OpenResource( slug ) )
  72. If Not icons
  73. Local atlas:=OpenImage( path )
  74. If Not atlas Return Null
  75. Local size:=atlas.Rect.Height
  76. Local n:=atlas.Rect.Width/size
  77. icons=New Icons
  78. icons.atlas=atlas
  79. icons.images=New Image[n]
  80. For Local i:=0 Until n
  81. Local image:=New Image( atlas,New Recti( i*size,0,i*size+size,size ) )
  82. icons.images[i]=image
  83. Next
  84. icons.AddDependancy( atlas )
  85. _res.AddResource( slug,icons )
  86. Endif
  87. For Local image:=Eachin icons.images
  88. image.Scale=_themeScale
  89. Next
  90. Return icons.images
  91. End
  92. #rem monkeydoc Loads a skin from a file.
  93. If `file` is an absolute path, this method is effectively the same as calling [[Skin.Load]].
  94. If `file` is not an absolute path, the skin is searched for in the following locations:
  95. * The theme directory.
  96. * The asset::images/ directory.
  97. #end
  98. Method OpenSkin:Skin( path:String )
  99. Local skin:Skin
  100. If Not ExtractRootDir( path ) skin=_res.OpenSkin( "theme::"+path )
  101. If Not skin skin=_res.OpenSkin( path )
  102. If Not skin Return Null
  103. skin.Image.Scale=_themeScale
  104. Return skin
  105. End
  106. Method Load:Bool( path:String,scale:Vec2f=New Vec2f( 1 ) )
  107. If Not ExtractRootDir( path ) path="theme::"+path
  108. If Not ExtractExt( path ) path+=".json"
  109. Local jobj:=JsonObject.Load( path )
  110. If Not jobj
  111. Print "Failed to load theme:"+path
  112. return False
  113. Endif
  114. _jcolors=jobj["colors"].ToObject()
  115. _jfonts=jobj["fonts"].ToObject()
  116. _jstyles=jobj["styles"].ToObject()
  117. _themeScale=scale
  118. Reload()
  119. Return True
  120. End
  121. Private
  122. Class Icons Extends Resource
  123. Field atlas:Image
  124. Field images:Image[]
  125. End
  126. Const _jdefault:=New JsonString( "default" )
  127. Field _res:ResourceManager
  128. Field _themeScale:Vec2f=New Vec2f( 1,1 )
  129. Field _jcolors:StringMap<JsonValue>
  130. Field _jfonts:StringMap<JsonValue>
  131. Field _jstyles:StringMap<JsonValue>
  132. Field _defaultStyle:Style
  133. Field _fonts:=New StringMap<Font>
  134. Field _colors:=New StringMap<Color>
  135. Field _styles:=New StringMap<Style>
  136. Field _cstyles:=New StringMap<Style>
  137. Method Unload()
  138. _fonts.Clear()
  139. _colors.Clear()
  140. _styles.Clear()
  141. _defaultStyle=Null
  142. End
  143. Method Reload()
  144. Unload()
  145. Local res:=_res
  146. _res=New ResourceManager
  147. _defaultStyle=LoadStyle( _jdefault )
  148. For Local it:=Eachin _jcolors
  149. LoadColor( New JsonString( it.Key ) )
  150. Next
  151. For Local it:=Eachin _jfonts
  152. LoadFont( New JsonString( it.Key ) )
  153. Next
  154. For Local it:=Eachin _jstyles
  155. LoadStyle( New JsonString( it.Key ) )
  156. Next
  157. If res res.Discard()
  158. ThemeChanged()
  159. End
  160. Method ToRect:Recti( jrect:JsonValue )
  161. Local arr:=jrect.ToArray()
  162. Local l:=0,t:=0,r:=0,b:=0
  163. Select arr.Length
  164. Case 1
  165. l=arr[0].ToNumber() ; r=l
  166. t=arr[0].ToNumber() ; b=t
  167. Case 2
  168. l=arr[0].ToNumber() ; r=l
  169. t=arr[1].ToNumber() ; b=t
  170. Case 4
  171. l=arr[0].ToNumber()
  172. t=arr[1].ToNumber()
  173. r=arr[2].ToNumber()
  174. b=arr[3].ToNumber()
  175. End
  176. l=l*_themeScale.x
  177. t=t*_themeScale.y
  178. r=r*_themeScale.x
  179. b=b*_themeScale.y
  180. Return New Recti( -l,-t,r,b )
  181. End
  182. Method LoadColor:Color( jcolor:JsonValue )
  183. Local jarr:=Cast<JsonArray>( jcolor )
  184. If jarr
  185. Local r:=0.0,g:=0.0,b:=0.0,a:=1.0
  186. Select jarr.Length
  187. Case 1
  188. r=jarr[0].ToNumber()
  189. g=jarr[0].ToNumber()
  190. b=jarr[0].ToNumber()
  191. Case 3
  192. r=jarr[0].ToNumber()
  193. g=jarr[1].ToNumber()
  194. b=jarr[2].ToNumber()
  195. Case 4
  196. r=jarr[0].ToNumber()
  197. g=jarr[1].ToNumber()
  198. b=jarr[2].ToNumber()
  199. a=jarr[3].ToNumber()
  200. Default
  201. Return Color.Magenta
  202. End
  203. Return New Color( r,g,b,a )
  204. Endif
  205. Local str:=jcolor.ToString()
  206. If str.StartsWith( "#" )
  207. Local a:=1.0,r:=0.0,g:=0.0,b:=0.0
  208. If str.Length=4 '#RGB
  209. r=StringToULong( str.Slice( 1,2 ),16 )/15.0
  210. g=StringToULong( str.Slice( 2,3 ),16 )/15.0
  211. b=StringToULong( str.Slice( 3,4 ),16 )/15.0
  212. Else If str.Length=5 '#ARGB
  213. a=StringToULong( str.Slice( 1,2 ),16 )/15.0
  214. r=StringToULong( str.Slice( 2,3 ),16 )/15.0
  215. g=StringToULong( str.Slice( 3,4 ),16 )/15.0
  216. b=StringToULong( str.Slice( 4,5 ),16 )/15.0
  217. Else If str.Length=7 '#RRGGBB
  218. r=StringToULong( str.Slice( 1,3 ),16 )/255.0
  219. g=StringToULong( str.Slice( 3,5 ),16 )/255.0
  220. b=StringToULong( str.Slice( 5,7 ),16 )/255.0
  221. Else If str.Length=9 '#AARRGGBB
  222. a=StringToULong( str.Slice( 1,3 ),16 )/255.0
  223. r=StringToULong( str.Slice( 3,5 ),16 )/255.0
  224. g=StringToULong( str.Slice( 5,7 ),16 )/255.0
  225. b=StringToULong( str.Slice( 7,9 ),16 )/255.0
  226. Else
  227. Return Color.Magenta
  228. Endif
  229. Return New Color( r,g,b,a )
  230. Endif
  231. If _colors.Contains( str ) Return _colors[str]
  232. jcolor=_jcolors[str]
  233. If Not jcolor Return Color.Magenta
  234. Local color:=LoadColor( jcolor )
  235. _colors[str]=color
  236. Return color
  237. End
  238. Method LoadFont:Font( name:JsonValue )
  239. Local str:=name.ToString()
  240. If _fonts.Contains( str ) Return _fonts[str]
  241. Local jfont:=_jfonts[str]
  242. If Not jfont Return App.DefaultFont
  243. Local fname:=jfont.ToString()
  244. Local fsize:=0
  245. Local i:=fname.Find( "," )
  246. If i<>-1
  247. fsize=Int( fname.Slice( i+1 ) )
  248. fname=fname.Slice( 0,i )
  249. Endif
  250. Local font:=OpenFont( fname,fsize )
  251. _fonts[str]=font
  252. Return font
  253. End
  254. Method SetStyle( style:Style,jstyle:StringMap<JsonValue> )
  255. If jstyle.Contains( "backgroundColor" ) style.BackgroundColor=LoadColor( jstyle["backgroundColor"] )
  256. If jstyle.Contains( "borderColor" ) style.BorderColor=LoadColor( jstyle["borderColor"] )
  257. If jstyle.Contains( "textColor" ) style.TextColor=LoadColor( jstyle["textColor"] )
  258. If jstyle.Contains( "iconColor" ) style.IconColor=LoadColor( jstyle["iconColor"] )
  259. If jstyle.Contains( "skinColor" ) style.SkinColor=LoadColor( jstyle["skinColor"] )
  260. If jstyle.Contains( "padding" ) style.Padding=ToRect( jstyle["padding"] )
  261. If jstyle.Contains( "border" ) style.Border=ToRect( jstyle["border"] )
  262. If jstyle.Contains( "margin" ) style.Margin=ToRect( jstyle["margin"] )
  263. If jstyle.Contains( "icons" ) style.Icons=OpenIcons( jstyle["icons"].ToString() )
  264. If jstyle.Contains( "skin" ) style.Skin=OpenSkin( jstyle["skin"].ToString() )
  265. If jstyle.Contains( "font" ) style.Font=LoadFont( jstyle["font"] )
  266. End
  267. Method LoadStyle:Style( name:JsonValue )
  268. Local str:=name.ToString()
  269. If _styles.Contains( str ) Return _styles[str]
  270. Local jobj:=_jstyles[str]
  271. If Not jobj Return _defaultStyle
  272. Local jstyle:=jobj.ToObject()
  273. 'get parent style
  274. '
  275. Local pstyle:Style
  276. If jstyle.Contains( "extends" )
  277. pstyle=LoadStyle( jstyle["extends"] )
  278. Else
  279. pstyle=_defaultStyle
  280. Endif
  281. 'create new style
  282. '
  283. Local style:Style
  284. Local cstyle:=_cstyles[str]
  285. If cstyle
  286. style=cstyle
  287. style.Set( pstyle )
  288. Else
  289. style=New Style( str,pstyle )
  290. Endif
  291. 'initialize
  292. '
  293. SetStyle( style,jstyle )
  294. For Local it:=Eachin style.States
  295. SetStyle( it.Value,jstyle )
  296. Next
  297. 'create states
  298. '
  299. If jstyle.Contains( "states" )
  300. local jstates:=jstyle["states"].ToObject()
  301. For Local it:=Eachin jstates
  302. Local state:=style.AddState( it.Key )
  303. SetStyle( state,it.Value.ToObject() )
  304. Next
  305. Endif
  306. _cstyles[str]=style
  307. _styles[str]=style
  308. Return style
  309. End
  310. End