builder.go 28 KB

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