builder.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  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. "path/filepath"
  10. "sort"
  11. "strconv"
  12. "strings"
  13. "github.com/g3n/engine/gui/assets/icon"
  14. "github.com/g3n/engine/math32"
  15. "gopkg.in/yaml.v2"
  16. )
  17. // Builder builds GUI objects from a declarative description in YAML format
  18. type Builder struct {
  19. desc map[string]*panelDesc // parsed descriptions
  20. imgpath string // base path for image panels files
  21. objpath strStack // stack of object names being built
  22. }
  23. type panelStyle struct {
  24. Borders string
  25. Paddings string
  26. BorderColor string
  27. BgColor string
  28. FgColor string
  29. }
  30. type panelStyles struct {
  31. Normal panelStyle
  32. Over panelStyle
  33. Focus panelStyle
  34. Pressed panelStyle
  35. Disabled panelStyle
  36. }
  37. type panelDesc struct {
  38. Type string // Gui object type: Panel, Label, Edit, etc ...
  39. Name string // Optional name for identification
  40. Position string // Optional position as: x y | x,y
  41. Width float32 // Optional width (default = 0)
  42. Height float32 // Optional height (default = 0)
  43. AspectWidth *float32 // Optional aspectwidth (default = nil)
  44. AspectHeight *float32 // Optional aspectwidth (default = nil)
  45. Margins string // Optional margins as 1 or 4 float values
  46. Borders string // Optional borders as 1 or 4 float values
  47. BorderColor string // Optional border color as name or 3 or 4 float values
  48. Paddings string // Optional paddings as 1 or 4 float values
  49. Color string // Optional color as 1 or 4 float values
  50. Enabled bool
  51. Visible bool
  52. Renderable bool
  53. Imagefile string // For Panel, Button
  54. Children []*panelDesc
  55. Layout layoutAttr
  56. Styles *panelStyles
  57. Text string // Label, Button
  58. BgColor string // Label
  59. FontColor string // Label
  60. FontSize *float32 // Label
  61. FontDPI *float32 // Label
  62. LineSpacing *float32 // Label
  63. PlaceHolder string // Edit
  64. MaxLength *uint // Edit
  65. Icon string // Button
  66. }
  67. type layoutAttr struct {
  68. Type string
  69. }
  70. const (
  71. descTypePanel = "Panel"
  72. descTypeImagePanel = "ImagePanel"
  73. descTypeLabel = "Label"
  74. descTypeIconLabel = "IconLabel"
  75. descTypeButton = "Button"
  76. descTypeEdit = "Edit"
  77. fieldMargins = "margins"
  78. fieldBorders = "borders"
  79. fieldBorderColor = "bordercolor"
  80. fieldPaddings = "paddings"
  81. fieldColor = "color"
  82. fieldBgColor = "bgcolor"
  83. )
  84. // NewBuilder creates and returns a pointer to a new gui Builder object
  85. func NewBuilder() *Builder {
  86. return new(Builder)
  87. }
  88. // ParseString parses a string with gui objects descriptions in YAML format
  89. // It there was a previously parsed description, it is cleared.
  90. func (b *Builder) ParseString(desc string) error {
  91. // Try assuming the description contains a single root panel
  92. var pd panelDesc
  93. err := yaml.Unmarshal([]byte(desc), &pd)
  94. if err != nil {
  95. return err
  96. }
  97. if pd.Type != "" {
  98. b.desc = make(map[string]*panelDesc)
  99. b.desc[""] = &pd
  100. return nil
  101. }
  102. // Try assuming the description is a map of panels
  103. var pdm map[string]*panelDesc
  104. err = yaml.Unmarshal([]byte(desc), &pdm)
  105. if err != nil {
  106. return err
  107. }
  108. b.desc = pdm
  109. return nil
  110. }
  111. // ParseFile builds gui objects from the specified file which
  112. // must contain objects descriptions in YAML format
  113. func (b *Builder) ParseFile(filepath string) error {
  114. // Reads all file data
  115. f, err := os.Open(filepath)
  116. if err != nil {
  117. return err
  118. }
  119. data, err := ioutil.ReadAll(f)
  120. if err != nil {
  121. return err
  122. }
  123. err = f.Close()
  124. if err != nil {
  125. return err
  126. }
  127. // Parses file data
  128. return b.ParseString(string(data))
  129. }
  130. // Names returns a sorted list of names of top level previously parsed objects.
  131. // Only objects with defined types are returned.
  132. // If there is only a single object with no name, its name is returned
  133. // as an empty string
  134. func (b *Builder) Names() []string {
  135. var objs []string
  136. for name, pd := range b.desc {
  137. if pd.Type != "" {
  138. objs = append(objs, name)
  139. }
  140. }
  141. sort.Strings(objs)
  142. return objs
  143. }
  144. // Build builds a gui object and all its children recursively.
  145. // The specified name should be a top level name from a
  146. // from a previously parsed description
  147. // If the descriptions contains a single object with no name,
  148. // It should be specified the empty string to build this object.
  149. func (b *Builder) Build(name string) (IPanel, error) {
  150. pd, ok := b.desc[name]
  151. if !ok {
  152. return nil, fmt.Errorf("Object name:%s not found", name)
  153. }
  154. b.objpath.clear()
  155. b.objpath.push(pd.Name)
  156. return b.build(pd, nil)
  157. }
  158. // Sets the path for image panels relative image files
  159. func (b *Builder) SetImagepath(path string) {
  160. b.imgpath = path
  161. }
  162. // build builds the gui object from the specified description.
  163. // All its children are also built recursively
  164. // Returns the built object or an error
  165. func (b *Builder) build(pd *panelDesc, iparent IPanel) (IPanel, error) {
  166. var err error
  167. var pan IPanel
  168. switch pd.Type {
  169. case descTypePanel:
  170. pan, err = b.buildPanel(pd)
  171. case descTypeImagePanel:
  172. pan, err = b.buildImagePanel(pd)
  173. case descTypeLabel:
  174. pan, err = b.buildLabel(pd)
  175. case descTypeIconLabel:
  176. pan, err = b.buildLabel(pd)
  177. case descTypeButton:
  178. pan, err = b.buildButton(pd)
  179. case descTypeEdit:
  180. pan, err = b.buildEdit(pd)
  181. default:
  182. err = fmt.Errorf("Invalid panel type:%s", pd.Type)
  183. }
  184. if err != nil {
  185. return nil, err
  186. }
  187. if iparent != nil {
  188. iparent.GetPanel().Add(pan)
  189. }
  190. return pan, nil
  191. }
  192. // buildPanel builds a gui object of type: "Panel"
  193. func (b *Builder) buildPanel(pd *panelDesc) (IPanel, error) {
  194. // Builds panel and set common attributes
  195. pan := NewPanel(pd.Width, pd.Height)
  196. err := b.setCommon(pd, pan)
  197. if err != nil {
  198. return nil, err
  199. }
  200. // Builds panel children recursively
  201. for i := 0; i < len(pd.Children); i++ {
  202. b.objpath.push(pd.Children[i].Name)
  203. child, err := b.build(pd.Children[i], pan)
  204. b.objpath.pop()
  205. if err != nil {
  206. return nil, err
  207. }
  208. pan.Add(child)
  209. }
  210. return pan, nil
  211. }
  212. // buildImagePanel builds a gui object of type: "ImagePanel"
  213. func (b *Builder) buildImagePanel(pd *panelDesc) (IPanel, error) {
  214. // Imagefile must be supplied
  215. if pd.Imagefile == "" {
  216. return nil, b.err("Imagefile", "Imagefile must be supplied")
  217. }
  218. // If path is not absolute join with user supplied image base path
  219. path := pd.Imagefile
  220. if !filepath.IsAbs(path) {
  221. path = filepath.Join(b.imgpath, path)
  222. }
  223. // Builds panel and set common attributes
  224. panel, err := NewImage(path)
  225. if err != nil {
  226. return nil, err
  227. }
  228. err = b.setCommon(pd, panel)
  229. if err != nil {
  230. return nil, err
  231. }
  232. // AspectWidth and AspectHeight attributes
  233. if pd.AspectWidth != nil {
  234. panel.SetContentAspectWidth(*pd.AspectWidth)
  235. }
  236. if pd.AspectHeight != nil {
  237. panel.SetContentAspectHeight(*pd.AspectHeight)
  238. }
  239. // Builds panel children recursively
  240. for i := 0; i < len(pd.Children); i++ {
  241. b.objpath.push(pd.Children[i].Name)
  242. child, err := b.build(pd.Children[i], panel)
  243. b.objpath.pop()
  244. if err != nil {
  245. return nil, err
  246. }
  247. panel.Add(child)
  248. }
  249. return panel, nil
  250. }
  251. // buildLabel builds a gui object of type: "Label"
  252. func (b *Builder) buildLabel(pd *panelDesc) (IPanel, error) {
  253. var label *Label
  254. if pd.Type == descTypeLabel {
  255. label = NewLabel(pd.Text)
  256. } else {
  257. icons, err := b.parseIconNames(pd.Name, "text", pd.Text)
  258. if err != nil {
  259. return nil, err
  260. }
  261. label = NewIconLabel(icons)
  262. }
  263. err := b.setCommon(pd, label)
  264. if err != nil {
  265. return nil, err
  266. }
  267. // Set optional background color
  268. c, err := b.parseColor(pd.Name, fieldBgColor, pd.BgColor)
  269. if err != nil {
  270. return nil, err
  271. }
  272. if c != nil {
  273. label.SetBgColor4(c)
  274. }
  275. // Set optional font color
  276. c, err = b.parseColor(pd.Name, "fontcolor", pd.FontColor)
  277. if err != nil {
  278. return nil, err
  279. }
  280. if c != nil {
  281. label.SetColor4(c)
  282. }
  283. // Sets optional font size
  284. if pd.FontSize != nil {
  285. label.SetFontSize(float64(*pd.FontSize))
  286. }
  287. // Sets optional font dpi
  288. if pd.FontDPI != nil {
  289. label.SetFontDPI(float64(*pd.FontDPI))
  290. }
  291. // Sets optional line spacing
  292. if pd.LineSpacing != nil {
  293. label.SetLineSpacing(float64(*pd.LineSpacing))
  294. }
  295. return label, nil
  296. }
  297. // buildButtonl builds a gui object of type: "Button"
  298. func (b *Builder) buildButton(pd *panelDesc) (IPanel, error) {
  299. // Builds button and set commont attributes
  300. button := NewButton(pd.Text)
  301. err := b.setCommon(pd, button)
  302. if err != nil {
  303. return nil, err
  304. }
  305. // Sets optional icon
  306. if pd.Icon != "" {
  307. cp, err := b.parseIconName(pd.Name, "icon", pd.Icon)
  308. if err != nil {
  309. return nil, err
  310. }
  311. button.SetIcon(int(cp))
  312. }
  313. // Sets optional image from file
  314. // If path is not absolute join with user supplied image base path
  315. if pd.Imagefile != "" {
  316. path := pd.Imagefile
  317. if !filepath.IsAbs(path) {
  318. path = filepath.Join(b.imgpath, path)
  319. }
  320. err := button.SetImage(path)
  321. if err != nil {
  322. return nil, err
  323. }
  324. }
  325. // Sets optional styles
  326. if pd.Styles != nil {
  327. err := b.setStyles(pd.Name, pd.Styles, button)
  328. if err != nil {
  329. return nil, err
  330. }
  331. }
  332. return button, nil
  333. }
  334. // buildEdit builds a gui object of type: "Edit"
  335. func (b *Builder) buildEdit(pa *panelDesc) (IPanel, error) {
  336. return nil, nil
  337. }
  338. // setCommon sets the common attributes in the description to the specified panel
  339. func (b *Builder) setCommon(pd *panelDesc, ipan IPanel) error {
  340. // Set optional position
  341. panel := ipan.GetPanel()
  342. if pd.Position != "" {
  343. va, err := b.parseFloats(pd.Name, "position", pd.Position, 2, 2)
  344. if va == nil || err != nil {
  345. return err
  346. }
  347. panel.SetPosition(va[0], va[1])
  348. }
  349. // Set optional margin sizes
  350. bs, err := b.parseBorderSizes(pd.Name, fieldMargins, pd.Margins)
  351. if err != nil {
  352. return err
  353. }
  354. if bs != nil {
  355. panel.SetMarginsFrom(bs)
  356. }
  357. // Set optional border sizes
  358. bs, err = b.parseBorderSizes(pd.Name, fieldBorders, pd.Borders)
  359. if err != nil {
  360. return err
  361. }
  362. if bs != nil {
  363. panel.SetBordersFrom(bs)
  364. }
  365. // Set optional border color
  366. c, err := b.parseColor(pd.Name, fieldBorderColor, pd.BorderColor)
  367. if err != nil {
  368. return err
  369. }
  370. if c != nil {
  371. panel.SetBordersColor4(c)
  372. }
  373. // Set optional paddings sizes
  374. bs, err = b.parseBorderSizes(pd.Name, fieldPaddings, pd.Paddings)
  375. if err != nil {
  376. return err
  377. }
  378. if bs != nil {
  379. panel.SetPaddingsFrom(bs)
  380. }
  381. // Set optional color
  382. c, err = b.parseColor(pd.Name, fieldColor, pd.Color)
  383. if err != nil {
  384. return err
  385. }
  386. if c != nil {
  387. panel.SetColor4(c)
  388. }
  389. return nil
  390. }
  391. func (b *Builder) setStyles(pname string, ps *panelStyles, ipan IPanel) error {
  392. return nil
  393. }
  394. // parseBorderSizes parses a string field which can contain one float value or
  395. // float values. In the first case all borders has the same width
  396. func (b *Builder) parseBorderSizes(pname, fname, field string) (*BorderSizes, error) {
  397. va, err := b.parseFloats(pname, fname, field, 1, 4)
  398. if va == nil || err != nil {
  399. return nil, err
  400. }
  401. if len(va) == 1 {
  402. return &BorderSizes{va[0], va[0], va[0], va[0]}, nil
  403. }
  404. return &BorderSizes{va[0], va[1], va[2], va[3]}, nil
  405. }
  406. // parseColor parses a string field which can contain a color name or
  407. // a list of 3 or 4 float values for the color components
  408. func (b *Builder) parseColor(pname, fname, field string) (*math32.Color4, error) {
  409. // Checks if field is empty
  410. field = strings.Trim(field, " ")
  411. if field == "" {
  412. return nil, nil
  413. }
  414. // If string has 1 or 2 fields it must be a color name and optional alpha
  415. parts := strings.Fields(field)
  416. if len(parts) == 1 || len(parts) == 2 {
  417. // First part must be a color name
  418. if !math32.IsColor(parts[0]) {
  419. return nil, b.err(fname, fmt.Sprintf("Invalid color name:%s", parts[0]))
  420. }
  421. c := math32.ColorName(parts[0])
  422. c4 := math32.Color4{c.R, c.G, c.B, 1}
  423. if len(parts) == 2 {
  424. val, err := strconv.ParseFloat(parts[1], 32)
  425. if err != nil {
  426. return nil, b.err(fname, fmt.Sprintf("Invalid float32 value:%s", parts[1]))
  427. }
  428. c4.A = float32(val)
  429. }
  430. return &c4, nil
  431. }
  432. // Accept 3 or 4 floats values
  433. va, err := b.parseFloats(pname, fname, field, 3, 4)
  434. if err != nil {
  435. return nil, err
  436. }
  437. if len(va) == 3 {
  438. return &math32.Color4{va[0], va[1], va[2], 1}, nil
  439. }
  440. return &math32.Color4{va[0], va[1], va[2], va[3]}, nil
  441. }
  442. // parseIconNames parses a string with a list of icon names or codepoints and
  443. // returns a string with the icons codepoints encoded in UTF8
  444. func (b *Builder) parseIconNames(pname, fname, field string) (string, error) {
  445. text := ""
  446. parts := strings.Fields(field)
  447. for i := 0; i < len(parts); i++ {
  448. cp, err := b.parseIconName(pname, fname, parts[i])
  449. if err != nil {
  450. return "", err
  451. }
  452. text = text + string(cp)
  453. }
  454. return text, nil
  455. }
  456. // parseIconName parses a string with an icon name or codepoint in hex
  457. // and returns the icon codepoints value and an error
  458. func (b *Builder) parseIconName(pname, fname, field string) (uint, error) {
  459. // Try name first
  460. cp := icon.Codepoint(field)
  461. if cp != 0 {
  462. return cp, nil
  463. }
  464. // Try to parse as hex value
  465. cp2, err := strconv.ParseUint(field, 16, 32)
  466. if err != nil {
  467. return 0, b.err(fname, fmt.Sprintf("Invalid icon codepoint value/name:%v", field))
  468. }
  469. return uint(cp2), nil
  470. }
  471. // parseFloats parses a string with a list of floats with the specified size
  472. // and returns a slice. The specified size is 0 any number of floats is allowed.
  473. // The individual values can be separated by spaces or commas
  474. func (b *Builder) parseFloats(pname, fname, field string, min, max int) ([]float32, error) {
  475. // Checks if field is empty
  476. field = strings.Trim(field, " ")
  477. if field == "" {
  478. return nil, nil
  479. }
  480. // Separate individual fields
  481. var parts []string
  482. if strings.Index(field, ",") < 0 {
  483. parts = strings.Fields(field)
  484. } else {
  485. parts = strings.Split(field, ",")
  486. }
  487. if len(parts) < min || len(parts) > max {
  488. return nil, b.err(fname, "Invalid number of float32 values")
  489. }
  490. // Parse each field value and appends to slice
  491. var values []float32
  492. for i := 0; i < len(parts); i++ {
  493. val, err := strconv.ParseFloat(strings.Trim(parts[i], " "), 32)
  494. if err != nil {
  495. return nil, b.err(fname, err.Error())
  496. }
  497. values = append(values, float32(val))
  498. }
  499. return values, nil
  500. }
  501. // err creates and returns an error for the current object, field name and with the specified message
  502. func (b *Builder) err(fname, msg string) error {
  503. return fmt.Errorf("Error in object:%s field:%s -> %s", b.objpath.path(), fname, msg)
  504. }
  505. // strStack is a stack of strings
  506. type strStack struct {
  507. stack []string
  508. }
  509. // clear removes all elements from the stack
  510. func (ss *strStack) clear() {
  511. ss.stack = []string{}
  512. }
  513. // push pushes a string to the top of the stack
  514. func (ss *strStack) push(v string) {
  515. ss.stack = append(ss.stack, v)
  516. }
  517. // pop removes and returns the string at the top of the stack.
  518. // Returns an empty string if the stack is empty
  519. func (ss *strStack) pop() string {
  520. if len(ss.stack) == 0 {
  521. return ""
  522. }
  523. length := len(ss.stack)
  524. v := ss.stack[length-1]
  525. ss.stack = ss.stack[:length-1]
  526. return v
  527. }
  528. // path returns a string composed of all the strings in the
  529. // stack separated by a forward slash.
  530. func (ss *strStack) path() string {
  531. return strings.Join(ss.stack, "/")
  532. }