theme.monkey2 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  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 LoadJson( path )
  108. Print "Failed to load theme:"+path
  109. return False
  110. Endif
  111. _themeScale=scale
  112. Reload()
  113. Return True
  114. End
  115. Private
  116. Class Icons Extends Resource
  117. Field atlas:Image
  118. Field images:Image[]
  119. End
  120. Const _jdefault:=New JsonString( "default" )
  121. Field _res:ResourceManager
  122. Field _themeScale:Vec2f=New Vec2f( 1,1 )
  123. Field _jcolors:StringMap<JsonValue>
  124. Field _jfonts:StringMap<JsonValue>
  125. Field _jstyles:StringMap<JsonValue>
  126. Field _defaultStyle:Style
  127. Field _fonts:=New StringMap<Font>
  128. Field _colors:=New StringMap<Color>
  129. Field _styles:=New StringMap<Style>
  130. Field _cstyles:=New StringMap<Style>
  131. Method LoadJson:Bool( path:String )
  132. If Not ExtractRootDir( path ) path="theme::"+path
  133. If Not ExtractExt( path ) path+=".json"
  134. Local jobj:=JsonObject.Load( path )
  135. If Not jobj Return False
  136. If jobj.Contains( "extends" )
  137. If Not LoadJson( jobj.GetString( "extends" ) ) Return False
  138. If jobj.Contains( "colors" )
  139. For Local it:=Eachin jobj["colors"].ToObject()
  140. _jcolors[it.Key]=it.Value
  141. Next
  142. Endif
  143. If jobj.Contains( "fonts" )
  144. For Local it:=Eachin jobj["fonts"].ToObject()
  145. _jfonts[it.Key]=it.Value
  146. Next
  147. Endif
  148. If jobj.Contains( "styles" )
  149. For Local it:=Eachin jobj["styles"].ToObject()
  150. _jstyles[it.Key]=it.Value
  151. Next
  152. Endif
  153. Return True
  154. Endif
  155. _jcolors=jobj["colors"].ToObject()
  156. _jfonts=jobj["fonts"].ToObject()
  157. _jstyles=jobj["styles"].ToObject()
  158. Return True
  159. End
  160. Method Unload()
  161. _fonts.Clear()
  162. _colors.Clear()
  163. _styles.Clear()
  164. _defaultStyle=Null
  165. End
  166. Method Reload()
  167. Unload()
  168. Local res:=_res
  169. _res=New ResourceManager
  170. _defaultStyle=LoadStyle( _jdefault )
  171. For Local it:=Eachin _jcolors
  172. LoadColor( New JsonString( it.Key ) )
  173. Next
  174. For Local it:=Eachin _jfonts
  175. LoadFont( New JsonString( it.Key ) )
  176. Next
  177. For Local it:=Eachin _jstyles
  178. LoadStyle( New JsonString( it.Key ) )
  179. Next
  180. If res res.Discard()
  181. ThemeChanged()
  182. End
  183. Method ToRect:Recti( jrect:JsonValue )
  184. Local arr:=jrect.ToArray()
  185. Local l:=0,t:=0,r:=0,b:=0
  186. Select arr.Length
  187. Case 1
  188. l=arr[0].ToNumber() ; r=l
  189. t=arr[0].ToNumber() ; b=t
  190. Case 2
  191. l=arr[0].ToNumber() ; r=l
  192. t=arr[1].ToNumber() ; b=t
  193. Case 4
  194. l=arr[0].ToNumber()
  195. t=arr[1].ToNumber()
  196. r=arr[2].ToNumber()
  197. b=arr[3].ToNumber()
  198. End
  199. l=l*_themeScale.x
  200. t=t*_themeScale.y
  201. r=r*_themeScale.x
  202. b=b*_themeScale.y
  203. Return New Recti( -l,-t,r,b )
  204. End
  205. Method LoadColor:Color( jcolor:JsonValue )
  206. Local jarr:=Cast<JsonArray>( jcolor )
  207. If jarr
  208. Local r:=0.0,g:=0.0,b:=0.0,a:=1.0
  209. Select jarr.Length
  210. Case 1
  211. r=jarr[0].ToNumber()
  212. g=jarr[0].ToNumber()
  213. b=jarr[0].ToNumber()
  214. Case 3
  215. r=jarr[0].ToNumber()
  216. g=jarr[1].ToNumber()
  217. b=jarr[2].ToNumber()
  218. Case 4
  219. r=jarr[0].ToNumber()
  220. g=jarr[1].ToNumber()
  221. b=jarr[2].ToNumber()
  222. a=jarr[3].ToNumber()
  223. Default
  224. Return Color.Magenta
  225. End
  226. Return New Color( r,g,b,a )
  227. Endif
  228. Local str:=jcolor.ToString()
  229. If str.StartsWith( "#" )
  230. Local a:=1.0,r:=0.0,g:=0.0,b:=0.0
  231. If str.Length=4 '#RGB
  232. r=StringToULong( str.Slice( 1,2 ),16 )/15.0
  233. g=StringToULong( str.Slice( 2,3 ),16 )/15.0
  234. b=StringToULong( str.Slice( 3,4 ),16 )/15.0
  235. Else If str.Length=5 '#ARGB
  236. a=StringToULong( str.Slice( 1,2 ),16 )/15.0
  237. r=StringToULong( str.Slice( 2,3 ),16 )/15.0
  238. g=StringToULong( str.Slice( 3,4 ),16 )/15.0
  239. b=StringToULong( str.Slice( 4,5 ),16 )/15.0
  240. Else If str.Length=7 '#RRGGBB
  241. r=StringToULong( str.Slice( 1,3 ),16 )/255.0
  242. g=StringToULong( str.Slice( 3,5 ),16 )/255.0
  243. b=StringToULong( str.Slice( 5,7 ),16 )/255.0
  244. Else If str.Length=9 '#AARRGGBB
  245. a=StringToULong( str.Slice( 1,3 ),16 )/255.0
  246. r=StringToULong( str.Slice( 3,5 ),16 )/255.0
  247. g=StringToULong( str.Slice( 5,7 ),16 )/255.0
  248. b=StringToULong( str.Slice( 7,9 ),16 )/255.0
  249. Else
  250. Return Color.Magenta
  251. Endif
  252. Return New Color( r,g,b,a )
  253. Endif
  254. If _colors.Contains( str ) Return _colors[str]
  255. jcolor=_jcolors[str]
  256. If Not jcolor Return Color.Magenta
  257. Local color:=LoadColor( jcolor )
  258. _colors[str]=color
  259. Return color
  260. End
  261. Method LoadFont:Font( name:JsonValue )
  262. Local str:=name.ToString()
  263. If _fonts.Contains( str ) Return _fonts[str]
  264. Local jfont:=_jfonts[str]
  265. If Not jfont Return App.DefaultFont
  266. Local fname:=jfont.ToString()
  267. Local fsize:=0
  268. Local i:=fname.Find( "," )
  269. If i<>-1
  270. fsize=Int( fname.Slice( i+1 ) )
  271. fname=fname.Slice( 0,i )
  272. Endif
  273. Local font:=OpenFont( fname,fsize )
  274. _fonts[str]=font
  275. Return font
  276. End
  277. Method SetStyle( style:Style,jstyle:StringMap<JsonValue> )
  278. If jstyle.Contains( "backgroundColor" ) style.BackgroundColor=LoadColor( jstyle["backgroundColor"] )
  279. If jstyle.Contains( "borderColor" ) style.BorderColor=LoadColor( jstyle["borderColor"] )
  280. If jstyle.Contains( "textColor" ) style.TextColor=LoadColor( jstyle["textColor"] )
  281. If jstyle.Contains( "iconColor" ) style.IconColor=LoadColor( jstyle["iconColor"] )
  282. If jstyle.Contains( "skinColor" ) style.SkinColor=LoadColor( jstyle["skinColor"] )
  283. If jstyle.Contains( "padding" ) style.Padding=ToRect( jstyle["padding"] )
  284. If jstyle.Contains( "border" ) style.Border=ToRect( jstyle["border"] )
  285. If jstyle.Contains( "margin" ) style.Margin=ToRect( jstyle["margin"] )
  286. If jstyle.Contains( "icons" ) style.Icons=OpenIcons( jstyle["icons"].ToString() )
  287. If jstyle.Contains( "skin" ) style.Skin=OpenSkin( jstyle["skin"].ToString() )
  288. If jstyle.Contains( "font" ) style.Font=LoadFont( jstyle["font"] )
  289. End
  290. Method LoadStyle:Style( name:JsonValue )
  291. Local str:=name.ToString()
  292. If _styles.Contains( str ) Return _styles[str]
  293. Local jobj:=_jstyles[str]
  294. If Not jobj Return _defaultStyle
  295. Local jstyle:=jobj.ToObject()
  296. 'get parent style
  297. '
  298. Local pstyle:Style
  299. If jstyle.Contains( "extends" )
  300. pstyle=LoadStyle( jstyle["extends"] )
  301. Else
  302. pstyle=_defaultStyle
  303. Endif
  304. 'create new style
  305. '
  306. Local style:Style
  307. Local cstyle:=_cstyles[str]
  308. If cstyle
  309. style=cstyle
  310. style.Set( pstyle )
  311. Else
  312. style=New Style( str,pstyle )
  313. Endif
  314. 'initialize
  315. '
  316. SetStyle( style,jstyle )
  317. For Local it:=Eachin style.States
  318. SetStyle( it.Value,jstyle )
  319. Next
  320. 'create states
  321. '
  322. If jstyle.Contains( "states" )
  323. local jstates:=jstyle["states"].ToObject()
  324. For Local it:=Eachin jstates
  325. Local state:=style.AddState( it.Key )
  326. SetStyle( state,it.Value.ToObject() )
  327. Next
  328. Endif
  329. _cstyles[str]=style
  330. _styles[str]=style
  331. Return style
  332. End
  333. End