builder.go 33 KB

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