builder.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950
  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. "github.com/g3n/engine/window"
  16. "gopkg.in/yaml.v2"
  17. )
  18. // Builder builds GUI objects from a declarative description in YAML format
  19. type Builder struct {
  20. desc map[string]*descPanel // parsed descriptions
  21. imgpath string // base path for image panels files
  22. objpath strStack // stack of object names being built
  23. }
  24. // descLayout describes all layout types
  25. type descLayout struct {
  26. Type string // HBox, VBox, Dock
  27. Spacing float32
  28. }
  29. // descPanel describes all panel types
  30. type descPanel struct {
  31. Type string // Gui object type: Panel, Label, Edit, etc ...
  32. Name string // Optional name for identification
  33. Position string // Optional position as: x y | x,y
  34. Width float32 // Optional width (default = 0)
  35. Height float32 // Optional height (default = 0)
  36. AspectWidth *float32 // Optional aspectwidth (default = nil)
  37. AspectHeight *float32 // Optional aspectwidth (default = nil)
  38. Margins string // Optional margins as 1 or 4 float values
  39. Borders string // Optional borders as 1 or 4 float values
  40. BorderColor string // Optional border color as name or 3 or 4 float values
  41. Paddings string // Optional paddings as 1 or 4 float values
  42. Color string // Optional color as 1 or 4 float values
  43. Enabled *bool
  44. Visible *bool
  45. Renderable *bool
  46. Imagefile string // For Panel, Button
  47. Children []*descPanel // Panel
  48. Layout *descLayout // Optional pointer to layout
  49. Text string // Label, Button
  50. Icons string // Label
  51. BgColor string // Label
  52. FontColor string // Label
  53. FontSize *float32 // Label
  54. FontDPI *float32 // Label
  55. LineSpacing *float32 // Label
  56. PlaceHolder string // Edit
  57. MaxLength *uint // Edit
  58. Icon string // Button
  59. Group string // RadioButton
  60. ImageLabel *descPanel // DropDown
  61. Items []*descPanel // Menu, MenuBar
  62. Shortcut string // Menu
  63. Value *float32 // Slider
  64. ScaleFactor *float32 // Slider
  65. }
  66. const (
  67. descTypePanel = "Panel"
  68. descTypeImagePanel = "ImagePanel"
  69. descTypeLabel = "Label"
  70. descTypeImageLabel = "ImageLabel"
  71. descTypeButton = "Button"
  72. descTypeCheckBox = "CheckBox"
  73. descTypeRadioButton = "RadioButton"
  74. descTypeEdit = "Edit"
  75. descTypeVList = "VList"
  76. descTypeHList = "HList"
  77. descTypeDropDown = "DropDown"
  78. descTypeHSlider = "HSlider"
  79. descTypeVSlider = "VSlider"
  80. descTypeHSplitter = "HSplitter"
  81. descTypeVSplitter = "VSplitter"
  82. descTypeMenuBar = "MenuBar"
  83. descTypeMenu = "Menu"
  84. fieldMargins = "margins"
  85. fieldBorders = "borders"
  86. fieldBorderColor = "bordercolor"
  87. fieldPaddings = "paddings"
  88. fieldColor = "color"
  89. fieldBgColor = "bgcolor"
  90. )
  91. const (
  92. aPOS = 1 << iota // attribute position
  93. aSIZE = 1 << iota // attribute size
  94. aNAME = 1 << iota // attribute name
  95. aMARGINS = 1 << iota // attribute margins widths
  96. aBORDERS = 1 << iota // attribute borders widths
  97. aBORDERCOLOR = 1 << iota // attribute border color
  98. aPADDINGS = 1 << iota // attribute paddings widths
  99. aCOLOR = 1 << iota // attribute panel bgcolor
  100. aENABLED = 1 << iota // attribute enabled for events
  101. aRENDER = 1 << iota // attribute renderable
  102. aVISIBLE = 1 << iota // attribute visible
  103. asPANEL = 0xFF // attribute set for panels
  104. asWIDGET = aPOS | aNAME | aENABLED | aVISIBLE // attribute set for widgets
  105. )
  106. // NewBuilder creates and returns a pointer to a new gui Builder object
  107. func NewBuilder() *Builder {
  108. return new(Builder)
  109. }
  110. // ParseString parses a string with gui objects descriptions in YAML format
  111. // It there was a previously parsed description, it is cleared.
  112. func (b *Builder) ParseString(desc string) error {
  113. // Try assuming the description contains a single root panel
  114. var dp descPanel
  115. err := yaml.Unmarshal([]byte(desc), &dp)
  116. if err != nil {
  117. return err
  118. }
  119. if dp.Type != "" {
  120. b.desc = make(map[string]*descPanel)
  121. b.desc[""] = &dp
  122. return nil
  123. }
  124. // Try assuming the description is a map of panels
  125. var dpm map[string]*descPanel
  126. err = yaml.Unmarshal([]byte(desc), &dpm)
  127. if err != nil {
  128. return err
  129. }
  130. b.desc = dpm
  131. return nil
  132. }
  133. // ParseFile parses a file with gui objects descriptions in YAML format
  134. // It there was a previously parsed description, it is cleared.
  135. func (b *Builder) ParseFile(filepath string) error {
  136. // Reads all file data
  137. f, err := os.Open(filepath)
  138. if err != nil {
  139. return err
  140. }
  141. data, err := ioutil.ReadAll(f)
  142. if err != nil {
  143. return err
  144. }
  145. err = f.Close()
  146. if err != nil {
  147. return err
  148. }
  149. // Parses file data
  150. return b.ParseString(string(data))
  151. }
  152. // Names returns a sorted list of names of top level previously parsed objects.
  153. // Only objects with defined types are returned.
  154. // If there is only a single object with no name, its name is returned
  155. // as an empty string
  156. func (b *Builder) Names() []string {
  157. var objs []string
  158. for name, pd := range b.desc {
  159. if pd.Type != "" {
  160. objs = append(objs, name)
  161. }
  162. }
  163. sort.Strings(objs)
  164. return objs
  165. }
  166. // Build builds a gui object and all its children recursively.
  167. // The specified name should be a top level name from a
  168. // from a previously parsed description
  169. // If the descriptions contains a single object with no name,
  170. // It should be specified the empty string to build this object.
  171. func (b *Builder) Build(name string) (IPanel, error) {
  172. pd, ok := b.desc[name]
  173. if !ok {
  174. return nil, fmt.Errorf("Object name:%s not found", name)
  175. }
  176. b.objpath.clear()
  177. b.objpath.push(pd.Name)
  178. return b.build(pd, nil)
  179. }
  180. // Sets the path for image panels relative image files
  181. func (b *Builder) SetImagepath(path string) {
  182. b.imgpath = path
  183. }
  184. // build builds the gui object from the specified description.
  185. // All its children are also built recursively
  186. // Returns the built object or an error
  187. func (b *Builder) build(pd *descPanel, iparent IPanel) (IPanel, error) {
  188. var err error
  189. var pan IPanel
  190. switch pd.Type {
  191. case descTypePanel:
  192. pan, err = b.buildPanel(pd)
  193. case descTypeImagePanel:
  194. pan, err = b.buildImagePanel(pd)
  195. case descTypeLabel:
  196. pan, err = b.buildLabel(pd)
  197. case descTypeImageLabel:
  198. pan, err = b.buildImageLabel(pd)
  199. case descTypeButton:
  200. pan, err = b.buildButton(pd)
  201. case descTypeCheckBox:
  202. pan, err = b.buildCheckBox(pd)
  203. case descTypeRadioButton:
  204. pan, err = b.buildRadioButton(pd)
  205. case descTypeEdit:
  206. pan, err = b.buildEdit(pd)
  207. case descTypeVList:
  208. pan, err = b.buildVList(pd)
  209. case descTypeHList:
  210. pan, err = b.buildHList(pd)
  211. case descTypeDropDown:
  212. pan, err = b.buildDropDown(pd)
  213. case descTypeHSlider:
  214. pan, err = b.buildSlider(pd, true)
  215. case descTypeVSlider:
  216. pan, err = b.buildSlider(pd, false)
  217. case descTypeHSplitter:
  218. pan, err = b.buildSplitter(pd, true)
  219. case descTypeVSplitter:
  220. pan, err = b.buildSplitter(pd, false)
  221. case descTypeMenuBar:
  222. pan, err = b.buildMenu(pd, false, true)
  223. case descTypeMenu:
  224. pan, err = b.buildMenu(pd, false, false)
  225. default:
  226. err = fmt.Errorf("Invalid panel type:%s", pd.Type)
  227. }
  228. if err != nil {
  229. return nil, err
  230. }
  231. if iparent != nil {
  232. iparent.GetPanel().Add(pan)
  233. }
  234. return pan, nil
  235. }
  236. // buildPanel builds a gui object of type: "Panel"
  237. func (b *Builder) buildPanel(pd *descPanel) (IPanel, error) {
  238. // Builds panel and set common attributes
  239. pan := NewPanel(pd.Width, pd.Height)
  240. err := b.setCommon(pd, pan, asPANEL)
  241. if err != nil {
  242. return nil, err
  243. }
  244. // Builds panel children recursively
  245. for i := 0; i < len(pd.Children); i++ {
  246. b.objpath.push(pd.Children[i].Name)
  247. child, err := b.build(pd.Children[i], pan)
  248. b.objpath.pop()
  249. if err != nil {
  250. return nil, err
  251. }
  252. pan.Add(child)
  253. }
  254. return pan, nil
  255. }
  256. // buildImagePanel builds a gui object of type: "ImagePanel"
  257. func (b *Builder) buildImagePanel(pd *descPanel) (IPanel, error) {
  258. // Imagefile must be supplied
  259. if pd.Imagefile == "" {
  260. return nil, b.err("Imagefile", "Imagefile must be supplied")
  261. }
  262. // If path is not absolute join with user supplied image base path
  263. path := pd.Imagefile
  264. if !filepath.IsAbs(path) {
  265. path = filepath.Join(b.imgpath, path)
  266. }
  267. // Builds panel and set common attributes
  268. panel, err := NewImage(path)
  269. if err != nil {
  270. return nil, err
  271. }
  272. err = b.setCommon(pd, panel, asPANEL)
  273. if err != nil {
  274. return nil, err
  275. }
  276. // AspectWidth and AspectHeight attributes
  277. if pd.AspectWidth != nil {
  278. panel.SetContentAspectWidth(*pd.AspectWidth)
  279. }
  280. if pd.AspectHeight != nil {
  281. panel.SetContentAspectHeight(*pd.AspectHeight)
  282. }
  283. // Builds panel children recursively
  284. for i := 0; i < len(pd.Children); i++ {
  285. b.objpath.push(pd.Children[i].Name)
  286. child, err := b.build(pd.Children[i], panel)
  287. b.objpath.pop()
  288. if err != nil {
  289. return nil, err
  290. }
  291. panel.Add(child)
  292. }
  293. return panel, nil
  294. }
  295. // buildLabel builds a gui object of type: "Label"
  296. func (b *Builder) buildLabel(pd *descPanel) (IPanel, error) {
  297. // Builds label with icon or text font
  298. var label *Label
  299. icons, err := b.parseIconNames("icons", pd.Icons)
  300. if err != nil {
  301. return nil, err
  302. }
  303. if icons != "" {
  304. label = NewLabel(icons, true)
  305. } else {
  306. label = NewLabel(pd.Text)
  307. }
  308. // Sets common attributes
  309. err = b.setCommon(pd, label, asPANEL)
  310. if err != nil {
  311. return nil, err
  312. }
  313. // Set optional background color
  314. c, err := b.parseColor(fieldBgColor, pd.BgColor)
  315. if err != nil {
  316. return nil, err
  317. }
  318. if c != nil {
  319. label.SetBgColor4(c)
  320. }
  321. // Set optional font color
  322. c, err = b.parseColor("fontcolor", pd.FontColor)
  323. if err != nil {
  324. return nil, err
  325. }
  326. if c != nil {
  327. label.SetColor4(c)
  328. }
  329. // Sets optional font size
  330. if pd.FontSize != nil {
  331. label.SetFontSize(float64(*pd.FontSize))
  332. }
  333. // Sets optional font dpi
  334. if pd.FontDPI != nil {
  335. label.SetFontDPI(float64(*pd.FontDPI))
  336. }
  337. // Sets optional line spacing
  338. if pd.LineSpacing != nil {
  339. label.SetLineSpacing(float64(*pd.LineSpacing))
  340. }
  341. return label, nil
  342. }
  343. // buildImageLabel builds a gui object of type: ImageLabel
  344. func (b *Builder) buildImageLabel(pd *descPanel) (IPanel, error) {
  345. // Builds image label and set common attributes
  346. imglabel := NewImageLabel(pd.Text)
  347. err := b.setCommon(pd, imglabel, asPANEL)
  348. if err != nil {
  349. return nil, err
  350. }
  351. // Sets optional icon(s)
  352. icons, err := b.parseIconNames("icons", pd.Icons)
  353. if err != nil {
  354. return nil, err
  355. }
  356. if icons != "" {
  357. imglabel.SetIcon(icons)
  358. }
  359. return imglabel, nil
  360. }
  361. // buildButton builds a gui object of type: Button
  362. func (b *Builder) buildButton(pd *descPanel) (IPanel, error) {
  363. // Builds button and set commont attributes
  364. button := NewButton(pd.Text)
  365. err := b.setCommon(pd, button, asWIDGET)
  366. if err != nil {
  367. return nil, err
  368. }
  369. // Sets optional icon
  370. if pd.Icon != "" {
  371. cp, err := b.parseIconName("icon", pd.Icon)
  372. if err != nil {
  373. return nil, err
  374. }
  375. button.SetIcon(cp)
  376. }
  377. // Sets optional image from file
  378. // If path is not absolute join with user supplied image base path
  379. if pd.Imagefile != "" {
  380. path := pd.Imagefile
  381. if !filepath.IsAbs(path) {
  382. path = filepath.Join(b.imgpath, path)
  383. }
  384. err := button.SetImage(path)
  385. if err != nil {
  386. return nil, err
  387. }
  388. }
  389. return button, nil
  390. }
  391. // buildCheckBox builds a gui object of type: CheckBox
  392. func (b *Builder) buildCheckBox(pd *descPanel) (IPanel, error) {
  393. // Builds check box and set commont attributes
  394. cb := NewCheckBox(pd.Text)
  395. err := b.setCommon(pd, cb, asWIDGET)
  396. if err != nil {
  397. return nil, err
  398. }
  399. return cb, nil
  400. }
  401. // buildRadioButton builds a gui object of type: RadioButton
  402. func (b *Builder) buildRadioButton(pd *descPanel) (IPanel, error) {
  403. // Builds check box and set commont attributes
  404. rb := NewRadioButton(pd.Text)
  405. err := b.setCommon(pd, rb, asWIDGET)
  406. if err != nil {
  407. return nil, err
  408. }
  409. // Sets optional radio button group
  410. if pd.Group != "" {
  411. rb.SetGroup(pd.Group)
  412. }
  413. return rb, nil
  414. }
  415. // buildEdit builds a gui object of type: "Edit"
  416. func (b *Builder) buildEdit(pd *descPanel) (IPanel, error) {
  417. // Builds button and set commont attributes
  418. edit := NewEdit(int(pd.Width), pd.PlaceHolder)
  419. err := b.setCommon(pd, edit, asWIDGET)
  420. if err != nil {
  421. return nil, err
  422. }
  423. edit.SetText(pd.Text)
  424. return edit, nil
  425. }
  426. // buildVList builds a gui object of type: VList
  427. func (b *Builder) buildVList(pd *descPanel) (IPanel, error) {
  428. // Builds list and set commont attributes
  429. list := NewVList(pd.Width, pd.Height)
  430. err := b.setCommon(pd, list, asWIDGET)
  431. if err != nil {
  432. return nil, err
  433. }
  434. // Builds list children
  435. for i := 0; i < len(pd.Children); i++ {
  436. b.objpath.push(pd.Children[i].Name)
  437. child, err := b.build(pd.Children[i], list)
  438. b.objpath.pop()
  439. if err != nil {
  440. return nil, err
  441. }
  442. list.Add(child)
  443. }
  444. return list, nil
  445. }
  446. // buildHList builds a gui object of type: VList
  447. func (b *Builder) buildHList(pd *descPanel) (IPanel, error) {
  448. // Builds list and set commont attributes
  449. list := NewHList(pd.Width, pd.Height)
  450. err := b.setCommon(pd, list, asWIDGET)
  451. if err != nil {
  452. return nil, err
  453. }
  454. // Builds list children
  455. for i := 0; i < len(pd.Children); i++ {
  456. b.objpath.push(pd.Children[i].Name)
  457. child, err := b.build(pd.Children[i], list)
  458. b.objpath.pop()
  459. if err != nil {
  460. return nil, err
  461. }
  462. list.Add(child)
  463. }
  464. return list, nil
  465. }
  466. // buildDropDown builds a gui object of type: DropDown
  467. func (b *Builder) buildDropDown(pd *descPanel) (IPanel, error) {
  468. var imglabel *ImageLabel
  469. if pd.ImageLabel != nil {
  470. pd.ImageLabel.Type = descTypeImageLabel
  471. ipan, err := b.build(pd.ImageLabel, nil)
  472. if err != nil {
  473. return nil, err
  474. }
  475. imglabel = ipan.(*ImageLabel)
  476. } else {
  477. imglabel = NewImageLabel("")
  478. }
  479. // Builds drop down and set common attributes
  480. dd := NewDropDown(pd.Width, imglabel)
  481. err := b.setCommon(pd, dd, asWIDGET)
  482. if err != nil {
  483. return nil, err
  484. }
  485. // Builds drop down children
  486. for i := 0; i < len(pd.Children); i++ {
  487. pdchild := pd.Children[i]
  488. pdchild.Type = descTypeImageLabel
  489. b.objpath.push(pdchild.Name)
  490. child, err := b.build(pdchild, dd)
  491. b.objpath.pop()
  492. if err != nil {
  493. return nil, err
  494. }
  495. dd.Add(child.(*ImageLabel))
  496. }
  497. return dd, nil
  498. }
  499. // buildSlider builds a gui object of type: HSlider or VSlider
  500. func (b *Builder) buildSlider(pd *descPanel, horiz bool) (IPanel, error) {
  501. // Builds slider and sets its position
  502. var slider *Slider
  503. if horiz {
  504. slider = NewHSlider(pd.Width, pd.Height)
  505. log.Error("slider:%v/%v", pd.Width, pd.Height)
  506. } else {
  507. slider = NewVSlider(pd.Width, pd.Height)
  508. }
  509. err := b.setCommon(pd, slider, asWIDGET)
  510. if err != nil {
  511. return nil, err
  512. }
  513. // Sets optional text
  514. if pd.Text != "" {
  515. slider.SetText(pd.Text)
  516. }
  517. // Sets optional scale factor
  518. if pd.ScaleFactor != nil {
  519. slider.SetScaleFactor(*pd.ScaleFactor)
  520. }
  521. // Sets optional value
  522. if pd.Value != nil {
  523. slider.SetValue(*pd.Value)
  524. }
  525. return slider, nil
  526. }
  527. // buildSplitter builds a gui object of type: HSplitterr or VSplitter
  528. func (b *Builder) buildSplitter(pd *descPanel, horiz bool) (IPanel, error) {
  529. // Builds splitter and sets its common attributes
  530. var splitter *Splitter
  531. if horiz {
  532. splitter = NewHSplitter(pd.Width, pd.Height)
  533. } else {
  534. splitter = NewVSplitter(pd.Width, pd.Height)
  535. }
  536. err := b.setCommon(pd, splitter, asWIDGET)
  537. if err != nil {
  538. return nil, err
  539. }
  540. return splitter, nil
  541. }
  542. // buildMenu builds a gui object of type: Menu or MenuBar from the
  543. // specified panel descriptor.
  544. func (b *Builder) buildMenu(pd *descPanel, child, bar bool) (IPanel, error) {
  545. // Builds menu bar or menu
  546. var menu *Menu
  547. if bar {
  548. menu = NewMenuBar()
  549. } else {
  550. menu = NewMenu()
  551. }
  552. // Only sets attribs for top level menus
  553. if !child {
  554. err := b.setCommon(pd, menu, asWIDGET)
  555. if err != nil {
  556. return nil, err
  557. }
  558. }
  559. // Builds and adds menu items
  560. for i := 0; i < len(pd.Items); i++ {
  561. item := pd.Items[i]
  562. // Item is another menu
  563. if item.Type == descTypeMenu {
  564. subm, err := b.buildMenu(item, true, false)
  565. if err != nil {
  566. return nil, err
  567. }
  568. menu.AddMenu(item.Text, subm.(*Menu))
  569. continue
  570. }
  571. // Item is a separator
  572. if item.Type == "Separator" {
  573. menu.AddSeparator()
  574. continue
  575. }
  576. // Item must be a menu option
  577. mi := menu.AddOption(item.Text)
  578. // Set item optional icon(s)
  579. icons, err := b.parseIconNames("icon", item.Icon)
  580. if err != nil {
  581. return nil, err
  582. }
  583. if icons != "" {
  584. mi.SetIcon(string(icons))
  585. }
  586. // Sets optional menu item shortcut
  587. err = b.setMenuShortcut(mi, "shortcut", item.Shortcut)
  588. if err != nil {
  589. return nil, err
  590. }
  591. }
  592. return menu, nil
  593. }
  594. // setCommon sets the common attributes in the description to the specified panel
  595. func (b *Builder) setCommon(pd *descPanel, ipan IPanel, attr uint) error {
  596. panel := ipan.GetPanel()
  597. // Set optional position
  598. if attr&aPOS != 0 && pd.Position != "" {
  599. va, err := b.parseFloats("position", pd.Position, 2, 2)
  600. if va == nil || err != nil {
  601. return err
  602. }
  603. panel.SetPosition(va[0], va[1])
  604. }
  605. // Set optional margin sizes
  606. if attr&aMARGINS != 0 {
  607. bs, err := b.parseBorderSizes(fieldMargins, pd.Margins)
  608. if err != nil {
  609. return err
  610. }
  611. if bs != nil {
  612. panel.SetMarginsFrom(bs)
  613. }
  614. }
  615. // Set optional border sizes
  616. if attr&aBORDERS != 0 {
  617. bs, err := b.parseBorderSizes(fieldBorders, pd.Borders)
  618. if err != nil {
  619. return err
  620. }
  621. if bs != nil {
  622. panel.SetBordersFrom(bs)
  623. }
  624. }
  625. // Set optional border color
  626. if attr&aBORDERCOLOR != 0 {
  627. c, err := b.parseColor(fieldBorderColor, pd.BorderColor)
  628. if err != nil {
  629. return err
  630. }
  631. if c != nil {
  632. panel.SetBordersColor4(c)
  633. }
  634. }
  635. // Set optional paddings sizes
  636. if attr&aPADDINGS != 0 {
  637. bs, err := b.parseBorderSizes(fieldPaddings, pd.Paddings)
  638. if err != nil {
  639. return err
  640. }
  641. if bs != nil {
  642. panel.SetPaddingsFrom(bs)
  643. }
  644. }
  645. // Set optional color
  646. if attr&aCOLOR != 0 {
  647. c, err := b.parseColor(fieldColor, pd.Color)
  648. if err != nil {
  649. return err
  650. }
  651. if c != nil {
  652. panel.SetColor4(c)
  653. }
  654. }
  655. if attr&aNAME != 0 && pd.Name != "" {
  656. panel.SetName(pd.Name)
  657. }
  658. if attr&aVISIBLE != 0 && pd.Visible != nil {
  659. panel.SetVisible(*pd.Visible)
  660. }
  661. if attr&aENABLED != 0 && pd.Enabled != nil {
  662. panel.SetEnabled(*pd.Enabled)
  663. }
  664. if attr&aRENDER != 0 && pd.Renderable != nil {
  665. panel.SetRenderable(*pd.Renderable)
  666. }
  667. return nil
  668. }
  669. func (b *Builder) setMenuShortcut(mi *MenuItem, fname, field string) error {
  670. field = strings.Trim(field, " ")
  671. if field == "" {
  672. return nil
  673. }
  674. parts := strings.Split(field, "+")
  675. var mods window.ModifierKey
  676. for i := 0; i < len(parts)-1; i++ {
  677. switch parts[i] {
  678. case "Shift":
  679. mods |= window.ModShift
  680. case "Ctrl":
  681. mods |= window.ModControl
  682. case "Alt":
  683. mods |= window.ModAlt
  684. default:
  685. return b.err(fname, "Invalid shortcut:"+field)
  686. }
  687. }
  688. // The last part must be a key
  689. key := parts[len(parts)-1]
  690. for kcode, kname := range mapKeyText {
  691. if kname == key {
  692. mi.SetShortcut(mods, kcode)
  693. return nil
  694. }
  695. }
  696. return b.err(fname, "Invalid shortcut:"+field)
  697. }
  698. // parseBorderSizes parses a string field which can contain one float value or
  699. // float values. In the first case all borders has the same width
  700. func (b *Builder) parseBorderSizes(fname, field string) (*BorderSizes, error) {
  701. va, err := b.parseFloats(fname, field, 1, 4)
  702. if va == nil || err != nil {
  703. return nil, err
  704. }
  705. if len(va) == 1 {
  706. return &BorderSizes{va[0], va[0], va[0], va[0]}, nil
  707. }
  708. return &BorderSizes{va[0], va[1], va[2], va[3]}, nil
  709. }
  710. // parseColor parses a string field which can contain a color name or
  711. // a list of 3 or 4 float values for the color components
  712. func (b *Builder) parseColor(fname, field string) (*math32.Color4, error) {
  713. // Checks if field is empty
  714. field = strings.Trim(field, " ")
  715. if field == "" {
  716. return nil, nil
  717. }
  718. // If string has 1 or 2 fields it must be a color name and optional alpha
  719. parts := strings.Fields(field)
  720. if len(parts) == 1 || len(parts) == 2 {
  721. // First part must be a color name
  722. if !math32.IsColor(parts[0]) {
  723. return nil, b.err(fname, fmt.Sprintf("Invalid color name:%s", parts[0]))
  724. }
  725. c := math32.ColorName(parts[0])
  726. c4 := math32.Color4{c.R, c.G, c.B, 1}
  727. if len(parts) == 2 {
  728. val, err := strconv.ParseFloat(parts[1], 32)
  729. if err != nil {
  730. return nil, b.err(fname, fmt.Sprintf("Invalid float32 value:%s", parts[1]))
  731. }
  732. c4.A = float32(val)
  733. }
  734. return &c4, nil
  735. }
  736. // Accept 3 or 4 floats values
  737. va, err := b.parseFloats(fname, field, 3, 4)
  738. if err != nil {
  739. return nil, err
  740. }
  741. if len(va) == 3 {
  742. return &math32.Color4{va[0], va[1], va[2], 1}, nil
  743. }
  744. return &math32.Color4{va[0], va[1], va[2], va[3]}, nil
  745. }
  746. // parseIconNames parses a string with a list of icon names or codepoints and
  747. // returns a string with the icons codepoints encoded in UTF8
  748. func (b *Builder) parseIconNames(fname, field string) (string, error) {
  749. text := ""
  750. parts := strings.Fields(field)
  751. for i := 0; i < len(parts); i++ {
  752. cp, err := b.parseIconName(fname, parts[i])
  753. if err != nil {
  754. return "", err
  755. }
  756. text = text + string(cp)
  757. }
  758. return text, nil
  759. }
  760. // parseIconName parses a string with an icon name or codepoint in hex
  761. // and returns the icon codepoints value and an error
  762. func (b *Builder) parseIconName(fname, field string) (string, error) {
  763. // Try name first
  764. cp := icon.Codepoint(field)
  765. if cp != "" {
  766. return cp, nil
  767. }
  768. // Try to parse as hex value
  769. cp2, err := strconv.ParseUint(field, 16, 32)
  770. if err != nil {
  771. return "", b.err(fname, fmt.Sprintf("Invalid icon codepoint value/name:%v", field))
  772. }
  773. return string(cp2), nil
  774. }
  775. // parseFloats parses a string with a list of floats with the specified size
  776. // and returns a slice. The specified size is 0 any number of floats is allowed.
  777. // The individual values can be separated by spaces or commas
  778. func (b *Builder) parseFloats(fname, field string, min, max int) ([]float32, error) {
  779. // Checks if field is empty
  780. field = strings.Trim(field, " ")
  781. if field == "" {
  782. return nil, nil
  783. }
  784. // Separate individual fields
  785. var parts []string
  786. if strings.Index(field, ",") < 0 {
  787. parts = strings.Fields(field)
  788. } else {
  789. parts = strings.Split(field, ",")
  790. }
  791. if len(parts) < min || len(parts) > max {
  792. return nil, b.err(fname, "Invalid number of float32 values")
  793. }
  794. // Parse each field value and appends to slice
  795. var values []float32
  796. for i := 0; i < len(parts); i++ {
  797. val, err := strconv.ParseFloat(strings.Trim(parts[i], " "), 32)
  798. if err != nil {
  799. return nil, b.err(fname, err.Error())
  800. }
  801. values = append(values, float32(val))
  802. }
  803. return values, nil
  804. }
  805. // err creates and returns an error for the current object, field name and with the specified message
  806. func (b *Builder) err(fname, msg string) error {
  807. return fmt.Errorf("Error in object:%s field:%s -> %s", b.objpath.path(), fname, msg)
  808. }
  809. // strStack is a stack of strings
  810. type strStack struct {
  811. stack []string
  812. }
  813. // clear removes all elements from the stack
  814. func (ss *strStack) clear() {
  815. ss.stack = []string{}
  816. }
  817. // push pushes a string to the top of the stack
  818. func (ss *strStack) push(v string) {
  819. ss.stack = append(ss.stack, v)
  820. }
  821. // pop removes and returns the string at the top of the stack.
  822. // Returns an empty string if the stack is empty
  823. func (ss *strStack) pop() string {
  824. if len(ss.stack) == 0 {
  825. return ""
  826. }
  827. length := len(ss.stack)
  828. v := ss.stack[length-1]
  829. ss.stack = ss.stack[:length-1]
  830. return v
  831. }
  832. // path returns a string composed of all the strings in the
  833. // stack separated by a forward slash.
  834. func (ss *strStack) path() string {
  835. return strings.Join(ss.stack, "/")
  836. }