builder.go 14 KB

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