builder.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  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. Icons string // Label
  59. BgColor string // Label
  60. FontColor string // Label
  61. FontSize *float32 // Label
  62. FontDPI *float32 // Label
  63. LineSpacing *float32 // Label
  64. PlaceHolder string // Edit
  65. MaxLength *uint // Edit
  66. Icon string // Button
  67. Group string // RadioButton
  68. }
  69. type layoutAttr struct {
  70. Type string
  71. }
  72. const (
  73. descTypePanel = "Panel"
  74. descTypeImagePanel = "ImagePanel"
  75. descTypeLabel = "Label"
  76. descTypeImageLabel = "ImageLabel"
  77. descTypeButton = "Button"
  78. descTypeCheckBox = "CheckBox"
  79. descTypeRadioButton = "RadioButton"
  80. descTypeEdit = "Edit"
  81. descTypeVList = "VList"
  82. descTypeHList = "HList"
  83. fieldMargins = "margins"
  84. fieldBorders = "borders"
  85. fieldBorderColor = "bordercolor"
  86. fieldPaddings = "paddings"
  87. fieldColor = "color"
  88. fieldBgColor = "bgcolor"
  89. )
  90. // NewBuilder creates and returns a pointer to a new gui Builder object
  91. func NewBuilder() *Builder {
  92. return new(Builder)
  93. }
  94. // ParseString parses a string with gui objects descriptions in YAML format
  95. // It there was a previously parsed description, it is cleared.
  96. func (b *Builder) ParseString(desc string) error {
  97. // Try assuming the description contains a single root panel
  98. var pd panelDesc
  99. err := yaml.Unmarshal([]byte(desc), &pd)
  100. if err != nil {
  101. return err
  102. }
  103. if pd.Type != "" {
  104. b.desc = make(map[string]*panelDesc)
  105. b.desc[""] = &pd
  106. return nil
  107. }
  108. // Try assuming the description is a map of panels
  109. var pdm map[string]*panelDesc
  110. err = yaml.Unmarshal([]byte(desc), &pdm)
  111. if err != nil {
  112. return err
  113. }
  114. b.desc = pdm
  115. return nil
  116. }
  117. // ParseFile builds gui objects from the specified file which
  118. // must contain objects descriptions in YAML format
  119. func (b *Builder) ParseFile(filepath string) error {
  120. // Reads all file data
  121. f, err := os.Open(filepath)
  122. if err != nil {
  123. return err
  124. }
  125. data, err := ioutil.ReadAll(f)
  126. if err != nil {
  127. return err
  128. }
  129. err = f.Close()
  130. if err != nil {
  131. return err
  132. }
  133. // Parses file data
  134. return b.ParseString(string(data))
  135. }
  136. // Names returns a sorted list of names of top level previously parsed objects.
  137. // Only objects with defined types are returned.
  138. // If there is only a single object with no name, its name is returned
  139. // as an empty string
  140. func (b *Builder) Names() []string {
  141. var objs []string
  142. for name, pd := range b.desc {
  143. if pd.Type != "" {
  144. objs = append(objs, name)
  145. }
  146. }
  147. sort.Strings(objs)
  148. return objs
  149. }
  150. // Build builds a gui object and all its children recursively.
  151. // The specified name should be a top level name from a
  152. // from a previously parsed description
  153. // If the descriptions contains a single object with no name,
  154. // It should be specified the empty string to build this object.
  155. func (b *Builder) Build(name string) (IPanel, error) {
  156. pd, ok := b.desc[name]
  157. if !ok {
  158. return nil, fmt.Errorf("Object name:%s not found", name)
  159. }
  160. b.objpath.clear()
  161. b.objpath.push(pd.Name)
  162. return b.build(pd, nil)
  163. }
  164. // Sets the path for image panels relative image files
  165. func (b *Builder) SetImagepath(path string) {
  166. b.imgpath = path
  167. }
  168. // build builds the gui object from the specified description.
  169. // All its children are also built recursively
  170. // Returns the built object or an error
  171. func (b *Builder) build(pd *panelDesc, iparent IPanel) (IPanel, error) {
  172. var err error
  173. var pan IPanel
  174. switch pd.Type {
  175. case descTypePanel:
  176. pan, err = b.buildPanel(pd)
  177. case descTypeImagePanel:
  178. pan, err = b.buildImagePanel(pd)
  179. case descTypeLabel:
  180. pan, err = b.buildLabel(pd)
  181. case descTypeImageLabel:
  182. pan, err = b.buildImageLabel(pd)
  183. case descTypeButton:
  184. pan, err = b.buildButton(pd)
  185. case descTypeCheckBox:
  186. pan, err = b.buildCheckBox(pd)
  187. case descTypeRadioButton:
  188. pan, err = b.buildRadioButton(pd)
  189. case descTypeEdit:
  190. pan, err = b.buildEdit(pd)
  191. case descTypeVList:
  192. pan, err = b.buildVList(pd)
  193. case descTypeHList:
  194. pan, err = b.buildHList(pd)
  195. default:
  196. err = fmt.Errorf("Invalid panel type:%s", pd.Type)
  197. }
  198. if err != nil {
  199. return nil, err
  200. }
  201. if iparent != nil {
  202. iparent.GetPanel().Add(pan)
  203. }
  204. return pan, nil
  205. }
  206. // buildPanel builds a gui object of type: "Panel"
  207. func (b *Builder) buildPanel(pd *panelDesc) (IPanel, error) {
  208. // Builds panel and set common attributes
  209. pan := NewPanel(pd.Width, pd.Height)
  210. err := b.setCommon(pd, pan)
  211. if err != nil {
  212. return nil, err
  213. }
  214. // Builds panel children recursively
  215. for i := 0; i < len(pd.Children); i++ {
  216. b.objpath.push(pd.Children[i].Name)
  217. child, err := b.build(pd.Children[i], pan)
  218. b.objpath.pop()
  219. if err != nil {
  220. return nil, err
  221. }
  222. pan.Add(child)
  223. }
  224. return pan, nil
  225. }
  226. // buildImagePanel builds a gui object of type: "ImagePanel"
  227. func (b *Builder) buildImagePanel(pd *panelDesc) (IPanel, error) {
  228. // Imagefile must be supplied
  229. if pd.Imagefile == "" {
  230. return nil, b.err("Imagefile", "Imagefile must be supplied")
  231. }
  232. // If path is not absolute join with user supplied image base path
  233. path := pd.Imagefile
  234. if !filepath.IsAbs(path) {
  235. path = filepath.Join(b.imgpath, path)
  236. }
  237. // Builds panel and set common attributes
  238. panel, err := NewImage(path)
  239. if err != nil {
  240. return nil, err
  241. }
  242. err = b.setCommon(pd, panel)
  243. if err != nil {
  244. return nil, err
  245. }
  246. // AspectWidth and AspectHeight attributes
  247. if pd.AspectWidth != nil {
  248. panel.SetContentAspectWidth(*pd.AspectWidth)
  249. }
  250. if pd.AspectHeight != nil {
  251. panel.SetContentAspectHeight(*pd.AspectHeight)
  252. }
  253. // Builds panel children recursively
  254. for i := 0; i < len(pd.Children); i++ {
  255. b.objpath.push(pd.Children[i].Name)
  256. child, err := b.build(pd.Children[i], panel)
  257. b.objpath.pop()
  258. if err != nil {
  259. return nil, err
  260. }
  261. panel.Add(child)
  262. }
  263. return panel, nil
  264. }
  265. // buildLabel builds a gui object of type: "Label"
  266. func (b *Builder) buildLabel(pd *panelDesc) (IPanel, error) {
  267. // Builds label with icon or text font
  268. var label *Label
  269. icons, err := b.parseIconNames("icons", pd.Icons)
  270. if err != nil {
  271. return nil, err
  272. }
  273. if icons != "" {
  274. label = NewLabel(icons, true)
  275. } else {
  276. label = NewLabel(pd.Text)
  277. }
  278. // Sets common attributes
  279. err = b.setCommon(pd, label)
  280. if err != nil {
  281. return nil, err
  282. }
  283. // Set optional background color
  284. c, err := b.parseColor(fieldBgColor, pd.BgColor)
  285. if err != nil {
  286. return nil, err
  287. }
  288. if c != nil {
  289. label.SetBgColor4(c)
  290. }
  291. // Set optional font color
  292. c, err = b.parseColor("fontcolor", pd.FontColor)
  293. if err != nil {
  294. return nil, err
  295. }
  296. if c != nil {
  297. label.SetColor4(c)
  298. }
  299. // Sets optional font size
  300. if pd.FontSize != nil {
  301. label.SetFontSize(float64(*pd.FontSize))
  302. }
  303. // Sets optional font dpi
  304. if pd.FontDPI != nil {
  305. label.SetFontDPI(float64(*pd.FontDPI))
  306. }
  307. // Sets optional line spacing
  308. if pd.LineSpacing != nil {
  309. label.SetLineSpacing(float64(*pd.LineSpacing))
  310. }
  311. return label, nil
  312. }
  313. // buildImageLabel builds a gui object of type: ImageLabel
  314. func (b *Builder) buildImageLabel(pd *panelDesc) (IPanel, error) {
  315. // Builds image label and set common attributes
  316. imglabel := NewImageLabel(pd.Text)
  317. err := b.setCommon(pd, imglabel)
  318. if err != nil {
  319. return nil, err
  320. }
  321. // Sets optional icon(s)
  322. icons, err := b.parseIconNames("icons", pd.Icons)
  323. if err != nil {
  324. return nil, err
  325. }
  326. if icons != "" {
  327. imglabel.SetIcon(icons)
  328. }
  329. return imglabel, nil
  330. }
  331. // buildButton builds a gui object of type: Button
  332. func (b *Builder) buildButton(pd *panelDesc) (IPanel, error) {
  333. // Builds button and set commont attributes
  334. button := NewButton(pd.Text)
  335. err := b.setCommon(pd, button)
  336. if err != nil {
  337. return nil, err
  338. }
  339. // Sets optional icon
  340. if pd.Icon != "" {
  341. cp, err := b.parseIconName("icon", pd.Icon)
  342. if err != nil {
  343. return nil, err
  344. }
  345. button.SetIcon(int(cp))
  346. }
  347. // Sets optional image from file
  348. // If path is not absolute join with user supplied image base path
  349. if pd.Imagefile != "" {
  350. path := pd.Imagefile
  351. if !filepath.IsAbs(path) {
  352. path = filepath.Join(b.imgpath, path)
  353. }
  354. err := button.SetImage(path)
  355. if err != nil {
  356. return nil, err
  357. }
  358. }
  359. return button, nil
  360. }
  361. // buildCheckBox builds a gui object of type: CheckBox
  362. func (b *Builder) buildCheckBox(pd *panelDesc) (IPanel, error) {
  363. // Builds check box and set commont attributes
  364. cb := NewCheckBox(pd.Text)
  365. err := b.setCommon(pd, cb)
  366. if err != nil {
  367. return nil, err
  368. }
  369. return cb, nil
  370. }
  371. // buildRadioButton builds a gui object of type: RadioButton
  372. func (b *Builder) buildRadioButton(pd *panelDesc) (IPanel, error) {
  373. // Builds check box and set commont attributes
  374. rb := NewRadioButton(pd.Text)
  375. err := b.setCommon(pd, rb)
  376. if err != nil {
  377. return nil, err
  378. }
  379. // Sets optional radio button group
  380. if pd.Group != "" {
  381. rb.SetGroup(pd.Group)
  382. }
  383. return rb, nil
  384. }
  385. // buildEdit builds a gui object of type: "Edit"
  386. func (b *Builder) buildEdit(pd *panelDesc) (IPanel, error) {
  387. // Builds button and set commont attributes
  388. edit := NewEdit(int(pd.Width), pd.PlaceHolder)
  389. err := b.setCommon(pd, edit)
  390. if err != nil {
  391. return nil, err
  392. }
  393. edit.SetText(pd.Text)
  394. return edit, nil
  395. }
  396. // buildVList builds a gui object of type: VList
  397. func (b *Builder) buildVList(pd *panelDesc) (IPanel, error) {
  398. // Builds list and set commont attributes
  399. list := NewVList(pd.Width, pd.Height)
  400. err := b.setCommon(pd, list)
  401. if err != nil {
  402. return nil, err
  403. }
  404. // Builds list children
  405. for i := 0; i < len(pd.Children); i++ {
  406. b.objpath.push(pd.Children[i].Name)
  407. child, err := b.build(pd.Children[i], list)
  408. b.objpath.pop()
  409. if err != nil {
  410. return nil, err
  411. }
  412. list.Add(child)
  413. }
  414. return list, nil
  415. }
  416. // buildHList builds a gui object of type: VList
  417. func (b *Builder) buildHList(pd *panelDesc) (IPanel, error) {
  418. // Builds list and set commont attributes
  419. list := NewHList(pd.Width, pd.Height)
  420. err := b.setCommon(pd, list)
  421. if err != nil {
  422. return nil, err
  423. }
  424. // Builds list children
  425. for i := 0; i < len(pd.Children); i++ {
  426. b.objpath.push(pd.Children[i].Name)
  427. child, err := b.build(pd.Children[i], list)
  428. b.objpath.pop()
  429. if err != nil {
  430. return nil, err
  431. }
  432. list.Add(child)
  433. }
  434. return list, nil
  435. }
  436. // setCommon sets the common attributes in the description to the specified panel
  437. func (b *Builder) setCommon(pd *panelDesc, ipan IPanel) error {
  438. // Set optional position
  439. panel := ipan.GetPanel()
  440. if pd.Position != "" {
  441. va, err := b.parseFloats("position", pd.Position, 2, 2)
  442. if va == nil || err != nil {
  443. return err
  444. }
  445. panel.SetPosition(va[0], va[1])
  446. }
  447. // Set optional margin sizes
  448. bs, err := b.parseBorderSizes(fieldMargins, pd.Margins)
  449. if err != nil {
  450. return err
  451. }
  452. if bs != nil {
  453. panel.SetMarginsFrom(bs)
  454. }
  455. // Set optional border sizes
  456. bs, err = b.parseBorderSizes(fieldBorders, pd.Borders)
  457. if err != nil {
  458. return err
  459. }
  460. if bs != nil {
  461. panel.SetBordersFrom(bs)
  462. }
  463. // Set optional border color
  464. c, err := b.parseColor(fieldBorderColor, pd.BorderColor)
  465. if err != nil {
  466. return err
  467. }
  468. if c != nil {
  469. panel.SetBordersColor4(c)
  470. }
  471. // Set optional paddings sizes
  472. bs, err = b.parseBorderSizes(fieldPaddings, pd.Paddings)
  473. if err != nil {
  474. return err
  475. }
  476. if bs != nil {
  477. panel.SetPaddingsFrom(bs)
  478. }
  479. // Set optional color
  480. c, err = b.parseColor(fieldColor, pd.Color)
  481. if err != nil {
  482. return err
  483. }
  484. if c != nil {
  485. panel.SetColor4(c)
  486. }
  487. return nil
  488. }
  489. // parseBorderSizes parses a string field which can contain one float value or
  490. // float values. In the first case all borders has the same width
  491. func (b *Builder) parseBorderSizes(fname, field string) (*BorderSizes, error) {
  492. va, err := b.parseFloats(fname, field, 1, 4)
  493. if va == nil || err != nil {
  494. return nil, err
  495. }
  496. if len(va) == 1 {
  497. return &BorderSizes{va[0], va[0], va[0], va[0]}, nil
  498. }
  499. return &BorderSizes{va[0], va[1], va[2], va[3]}, nil
  500. }
  501. // parseColor parses a string field which can contain a color name or
  502. // a list of 3 or 4 float values for the color components
  503. func (b *Builder) parseColor(fname, field string) (*math32.Color4, error) {
  504. // Checks if field is empty
  505. field = strings.Trim(field, " ")
  506. if field == "" {
  507. return nil, nil
  508. }
  509. // If string has 1 or 2 fields it must be a color name and optional alpha
  510. parts := strings.Fields(field)
  511. if len(parts) == 1 || len(parts) == 2 {
  512. // First part must be a color name
  513. if !math32.IsColor(parts[0]) {
  514. return nil, b.err(fname, fmt.Sprintf("Invalid color name:%s", parts[0]))
  515. }
  516. c := math32.ColorName(parts[0])
  517. c4 := math32.Color4{c.R, c.G, c.B, 1}
  518. if len(parts) == 2 {
  519. val, err := strconv.ParseFloat(parts[1], 32)
  520. if err != nil {
  521. return nil, b.err(fname, fmt.Sprintf("Invalid float32 value:%s", parts[1]))
  522. }
  523. c4.A = float32(val)
  524. }
  525. return &c4, nil
  526. }
  527. // Accept 3 or 4 floats values
  528. va, err := b.parseFloats(fname, field, 3, 4)
  529. if err != nil {
  530. return nil, err
  531. }
  532. if len(va) == 3 {
  533. return &math32.Color4{va[0], va[1], va[2], 1}, nil
  534. }
  535. return &math32.Color4{va[0], va[1], va[2], va[3]}, nil
  536. }
  537. // parseIconNames parses a string with a list of icon names or codepoints and
  538. // returns a string with the icons codepoints encoded in UTF8
  539. func (b *Builder) parseIconNames(fname, field string) (string, error) {
  540. text := ""
  541. parts := strings.Fields(field)
  542. for i := 0; i < len(parts); i++ {
  543. cp, err := b.parseIconName(fname, parts[i])
  544. if err != nil {
  545. return "", err
  546. }
  547. text = text + string(cp)
  548. }
  549. return text, nil
  550. }
  551. // parseIconName parses a string with an icon name or codepoint in hex
  552. // and returns the icon codepoints value and an error
  553. func (b *Builder) parseIconName(fname, field string) (uint, error) {
  554. // Try name first
  555. cp := icon.Codepoint(field)
  556. if cp != 0 {
  557. return cp, nil
  558. }
  559. // Try to parse as hex value
  560. cp2, err := strconv.ParseUint(field, 16, 32)
  561. if err != nil {
  562. return 0, b.err(fname, fmt.Sprintf("Invalid icon codepoint value/name:%v", field))
  563. }
  564. return uint(cp2), nil
  565. }
  566. // parseFloats parses a string with a list of floats with the specified size
  567. // and returns a slice. The specified size is 0 any number of floats is allowed.
  568. // The individual values can be separated by spaces or commas
  569. func (b *Builder) parseFloats(fname, field string, min, max int) ([]float32, error) {
  570. // Checks if field is empty
  571. field = strings.Trim(field, " ")
  572. if field == "" {
  573. return nil, nil
  574. }
  575. // Separate individual fields
  576. var parts []string
  577. if strings.Index(field, ",") < 0 {
  578. parts = strings.Fields(field)
  579. } else {
  580. parts = strings.Split(field, ",")
  581. }
  582. if len(parts) < min || len(parts) > max {
  583. return nil, b.err(fname, "Invalid number of float32 values")
  584. }
  585. // Parse each field value and appends to slice
  586. var values []float32
  587. for i := 0; i < len(parts); i++ {
  588. val, err := strconv.ParseFloat(strings.Trim(parts[i], " "), 32)
  589. if err != nil {
  590. return nil, b.err(fname, err.Error())
  591. }
  592. values = append(values, float32(val))
  593. }
  594. return values, nil
  595. }
  596. // err creates and returns an error for the current object, field name and with the specified message
  597. func (b *Builder) err(fname, msg string) error {
  598. return fmt.Errorf("Error in object:%s field:%s -> %s", b.objpath.path(), fname, msg)
  599. }
  600. // strStack is a stack of strings
  601. type strStack struct {
  602. stack []string
  603. }
  604. // clear removes all elements from the stack
  605. func (ss *strStack) clear() {
  606. ss.stack = []string{}
  607. }
  608. // push pushes a string to the top of the stack
  609. func (ss *strStack) push(v string) {
  610. ss.stack = append(ss.stack, v)
  611. }
  612. // pop removes and returns the string at the top of the stack.
  613. // Returns an empty string if the stack is empty
  614. func (ss *strStack) pop() string {
  615. if len(ss.stack) == 0 {
  616. return ""
  617. }
  618. length := len(ss.stack)
  619. v := ss.stack[length-1]
  620. ss.stack = ss.stack[:length-1]
  621. return v
  622. }
  623. // path returns a string composed of all the strings in the
  624. // stack separated by a forward slash.
  625. func (ss *strStack) path() string {
  626. return strings.Join(ss.stack, "/")
  627. }