builder.go 30 KB

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