builder.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. // Copyright 2016 The G3N Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package gui
  5. import (
  6. "fmt"
  7. "io/ioutil"
  8. "os"
  9. "strconv"
  10. "strings"
  11. "github.com/g3n/engine/math32"
  12. "gopkg.in/yaml.v2"
  13. )
  14. // Builder builds GUI objects from a declarative description in YAML format
  15. type Builder struct {
  16. desc map[string]*panelDesc
  17. panels []IPanel // first level panels
  18. }
  19. type panelDesc struct {
  20. Type string
  21. Posx *float32
  22. Posy *float32
  23. Width float32
  24. Height float32
  25. Margins string
  26. Borders string
  27. BorderColor string
  28. Paddings string
  29. Color string
  30. Enabled bool
  31. Visible bool
  32. Renderable bool
  33. Children []panelDesc
  34. Layout layoutAttr
  35. Text string
  36. FontSize float32
  37. FontDPI float32
  38. PlaceHolder string
  39. MaxLength uint
  40. }
  41. type layoutAttr struct {
  42. Type string
  43. }
  44. const (
  45. descPanel = "Panel"
  46. descLabel = "Label"
  47. descEdit = "Edit"
  48. fieldMargins = "margins"
  49. fieldBorders = "borders"
  50. fieldBorderColor = "bordercolor"
  51. fieldPaddings = "paddings"
  52. fieldColor = "color"
  53. objSingle = "--single--"
  54. )
  55. //
  56. // NewBuilder creates and returns a pointer to a new gui Builder object
  57. //
  58. func NewBuilder() *Builder {
  59. b := new(Builder)
  60. return b
  61. }
  62. //
  63. // BuildFromData builds gui objects from the specified description
  64. // in YAML format
  65. //
  66. func (b *Builder) BuildFromString(desc string) error {
  67. // Try assuming the description contains a single root panel
  68. var pd panelDesc
  69. err := yaml.Unmarshal([]byte(desc), &pd)
  70. if err != nil {
  71. return err
  72. }
  73. if pd.Type != "" {
  74. pan, err := b.build(&pd, "", nil)
  75. if err != nil {
  76. return err
  77. }
  78. b.panels = append(b.panels, pan)
  79. return nil
  80. }
  81. // Try assuming the description is a map of panels
  82. var pdm map[string]panelDesc
  83. err = yaml.Unmarshal([]byte(desc), &pdm)
  84. if err != nil {
  85. return err
  86. }
  87. // Builds each panel in the map
  88. for name, pd := range pdm {
  89. pan, err := b.build(&pd, name, nil)
  90. if err != nil {
  91. return err
  92. }
  93. b.panels = append(b.panels, pan)
  94. }
  95. return nil
  96. }
  97. //
  98. // BuildFromFile builds gui objects from the specified file which
  99. // must contain objects descriptions in YAML format
  100. //
  101. func (b *Builder) BuildFromFile(filepath string) error {
  102. // Reads all file data
  103. f, err := os.Open(filepath)
  104. if err != nil {
  105. return err
  106. }
  107. data, err := ioutil.ReadAll(f)
  108. if err != nil {
  109. return err
  110. }
  111. err = f.Close()
  112. if err != nil {
  113. return err
  114. }
  115. // Parses file data
  116. return b.BuildFromString(string(data))
  117. }
  118. func (b *Builder) Panels() []IPanel {
  119. return b.panels
  120. }
  121. func (b *Builder) Label(path string) (*Label, error) {
  122. return nil, nil
  123. }
  124. //
  125. // build builds gui objects from the specified description and its children recursively
  126. //
  127. func (b *Builder) build(pd *panelDesc, pname string, parent *Panel) (IPanel, error) {
  128. fmt.Printf("\n%+v\n\n", pd)
  129. var err error
  130. var pan IPanel
  131. switch pd.Type {
  132. case descPanel:
  133. pan, err = b.buildPanel(pd, pname)
  134. case descLabel:
  135. pan, err = b.buildLabel(pd, pname)
  136. case descEdit:
  137. pan, err = b.buildEdit(pd, pname)
  138. default:
  139. err = fmt.Errorf("Invalid panel type:%s", pd.Type)
  140. }
  141. if err != nil {
  142. return nil, err
  143. }
  144. if parent != nil {
  145. parent.Add(pan)
  146. }
  147. return pan, nil
  148. }
  149. func (b *Builder) buildPanel(pd *panelDesc, pname string) (IPanel, error) {
  150. log.Error("buildPanel:[%s]", pd.Borders)
  151. pan := NewPanel(pd.Width, pd.Height)
  152. // Set margin sizes
  153. bs, err := b.parseBorderSizes(pname, fieldMargins, pd.Margins)
  154. if err != nil {
  155. return nil, err
  156. }
  157. if bs != nil {
  158. pan.SetMarginsFrom(bs)
  159. }
  160. // Set border sizes
  161. bs, err = b.parseBorderSizes(pname, fieldBorders, pd.Borders)
  162. if err != nil {
  163. return nil, err
  164. }
  165. if bs != nil {
  166. pan.SetBordersFrom(bs)
  167. }
  168. // Set border color
  169. c, err := b.parseColor(pname, fieldBorderColor, pd.BorderColor)
  170. if err != nil {
  171. return nil, err
  172. }
  173. if c != nil {
  174. pan.SetBordersColor4(c)
  175. }
  176. // Set paddings sizes
  177. bs, err = b.parseBorderSizes(pname, fieldPaddings, pd.Paddings)
  178. if err != nil {
  179. return nil, err
  180. }
  181. if bs != nil {
  182. pan.SetPaddingsFrom(bs)
  183. }
  184. // Set color
  185. c, err = b.parseColor(pname, fieldColor, pd.Color)
  186. if err != nil {
  187. return nil, err
  188. }
  189. if c != nil {
  190. pan.SetColor4(c)
  191. }
  192. return pan, nil
  193. }
  194. func (b *Builder) buildLabel(pa *panelDesc, name string) (IPanel, error) {
  195. label := NewLabel("df")
  196. return label, nil
  197. }
  198. func (b *Builder) buildEdit(pa *panelDesc, name string) (IPanel, error) {
  199. return nil, nil
  200. }
  201. //
  202. // parseBorderSizes parses a string field which can contain one float value or
  203. // float values. In the first case all borders has the same width
  204. //
  205. func (b *Builder) parseBorderSizes(pname, fname, field string) (*BorderSizes, error) {
  206. va, err := b.parseFloats(pname, fname, field, 1, 4)
  207. if va == nil || err != nil {
  208. return nil, err
  209. }
  210. if len(va) == 1 {
  211. return &BorderSizes{va[0], va[0], va[0], va[0]}, nil
  212. }
  213. return &BorderSizes{va[0], va[1], va[2], va[3]}, nil
  214. }
  215. //
  216. // parseColor parses a string field which can contain a color name or
  217. // a list of 3 or 4 float values for the color components
  218. //
  219. func (b *Builder) parseColor(pname, fname, field string) (*math32.Color4, error) {
  220. // Checks if field is empty
  221. field = strings.Trim(field, " ")
  222. if field == "" {
  223. return nil, nil
  224. }
  225. // Checks if field is a color name
  226. value := math32.ColorUint(field)
  227. if value != 0 {
  228. var c math32.Color
  229. c.SetName(field)
  230. return &math32.Color4{c.R, c.G, c.B, 1}, nil
  231. }
  232. // Accept 3 or 4 floats values
  233. va, err := b.parseFloats(pname, fname, field, 3, 4)
  234. if err != nil {
  235. return nil, err
  236. }
  237. if len(va) == 3 {
  238. return &math32.Color4{va[0], va[1], va[2], 1}, nil
  239. }
  240. return &math32.Color4{va[0], va[1], va[2], va[3]}, nil
  241. }
  242. //
  243. // parseFloats parses a string with a list of floats with the specified size
  244. // and returns a slice. The specified size is 0 any number of floats is allowed.
  245. // The individual values can be separated by spaces or commas
  246. //
  247. func (b *Builder) parseFloats(pname, fname, field string, min, max int) ([]float32, error) {
  248. // Checks if field is empty
  249. field = strings.Trim(field, " ")
  250. if field == "" {
  251. return nil, nil
  252. }
  253. // Separate individual fields
  254. var parts []string
  255. if strings.Index(field, ",") < 0 {
  256. parts = strings.Split(field, " ")
  257. } else {
  258. parts = strings.Split(field, ",")
  259. }
  260. if len(parts) < min || len(parts) > max {
  261. return nil, b.err(pname, fname, "Invalid number of float32 values")
  262. }
  263. // Parse each field value and appends to slice
  264. var values []float32
  265. for i := 0; i < len(parts); i++ {
  266. val, err := strconv.ParseFloat(parts[i], 32)
  267. if err != nil {
  268. return nil, fmt.Errorf("Error parsing float32 field:[%s]: %s", field, err)
  269. }
  270. values = append(values, float32(val))
  271. }
  272. return values, nil
  273. }
  274. func (b *Builder) err(pname, fname, msg string) error {
  275. return fmt.Errorf("Error in object:%s field:%s -> %s", pname, fname, msg)
  276. }