builder.go 7.4 KB

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