theme.monkey2 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  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, [[AppInstance.DefaultFont|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, [[std:std.graphics.Color.Grey|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. _res.AddResource( slug,icons )
  85. Endif
  86. For Local image:=Eachin icons.images
  87. image.Scale=_themeScale
  88. Next
  89. Return icons.images
  90. End
  91. #rem monkeydoc Loads a skin from a file.
  92. If `file` is an absolute path, this method is effectively the same as calling [[Skin.Load]].
  93. If `file` is not an absolute path, the skin is searched for in the following locations:
  94. * The theme directory.
  95. * The asset::images/ directory.
  96. #end
  97. Method OpenSkin:Skin( path:String )
  98. Local skin:Skin
  99. If Not ExtractRootDir( path ) skin=_res.OpenSkin( "theme::"+path )
  100. If Not skin skin=_res.OpenSkin( path )
  101. If Not skin Return Null
  102. skin.Image.Scale=_themeScale
  103. Return skin
  104. End
  105. Method Load:Bool( path:String,scale:Vec2f=New Vec2f( 1 ) )
  106. If Not LoadJson( path )
  107. Print "Failed to load theme:"+path
  108. return False
  109. Endif
  110. _themeScale=scale
  111. Reload()
  112. Return True
  113. End
  114. Private
  115. Class Icons Extends Resource
  116. Field atlas:Image
  117. Field images:Image[]
  118. End
  119. Const _jdefault:=New JsonString( "default" )
  120. Field _res:ResourceManager
  121. Field _themeScale:Vec2f=New Vec2f( 1,1 )
  122. Field _jcolors:StringMap<JsonValue>
  123. Field _jfonts:StringMap<JsonValue>
  124. Field _jstyles:StringMap<JsonValue>
  125. Field _defaultStyle:Style
  126. Field _fonts:=New StringMap<Font>
  127. Field _colors:=New StringMap<Color>
  128. Field _styles:=New StringMap<Style>
  129. Field _cstyles:=New StringMap<Style>
  130. Method LoadJson:Bool( path:String )
  131. If Not ExtractRootDir( path ) path="theme::"+path
  132. If Not ExtractExt( path ) path+=".json"
  133. Local jobj:=JsonObject.Load( path )
  134. If Not jobj Return False
  135. If jobj.Contains( "extends" )
  136. If Not LoadJson( jobj.GetString( "extends" ) ) Return False
  137. If jobj.Contains( "colors" )
  138. For Local it:=Eachin jobj["colors"].ToObject()
  139. _jcolors[it.Key]=it.Value
  140. Next
  141. Endif
  142. If jobj.Contains( "fonts" )
  143. For Local it:=Eachin jobj["fonts"].ToObject()
  144. _jfonts[it.Key]=it.Value
  145. Next
  146. Endif
  147. If jobj.Contains( "styles" )
  148. For Local it:=Eachin jobj["styles"].ToObject()
  149. _jstyles[it.Key]=it.Value
  150. Next
  151. Endif
  152. Return True
  153. Endif
  154. _jcolors=jobj["colors"].ToObject()
  155. _jfonts=jobj["fonts"].ToObject()
  156. _jstyles=jobj["styles"].ToObject()
  157. Return True
  158. End
  159. Method Unload()
  160. _fonts.Clear()
  161. _colors.Clear()
  162. _styles.Clear()
  163. _defaultStyle=New Style
  164. _defaultStyle.Font=App.DefaultFont
  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