tabbar.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  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. "github.com/g3n/engine/math32"
  8. "github.com/g3n/engine/window"
  9. )
  10. // TabBar is a panel which can contain other panels arranged in horizontal Tabs.
  11. // Only one panel is visible at a time.
  12. // To show another panel the corresponding Tab must be selected.
  13. type TabBar struct {
  14. Panel // Embedded panel
  15. styles *TabBarStyles // Pointer to current styles
  16. tabs []*Tab // Array of tabs
  17. separator Panel // Separator Panel
  18. listButton *Label // Icon for tab list button
  19. list *List // List for not visible tabs
  20. selected int // Index of the selected tab
  21. cursorOver bool // Cursor over TabBar panel flag
  22. }
  23. // TabBarStyle describes the style of the TabBar
  24. type TabBarStyle struct {
  25. Border BorderSizes // Border sizes
  26. Paddings BorderSizes // Padding sizes
  27. BorderColor math32.Color4 // Border color
  28. BgColor math32.Color4 // Background color
  29. }
  30. // TabBarStyles describes all the TabBarStyles
  31. type TabBarStyles struct {
  32. SepHeight float32 // Separator width
  33. ListButtonIcon string // Icon for list button
  34. ListButtonPaddings BorderSizes // Paddings for list button
  35. Normal TabBarStyle // Style for normal exhibition
  36. Over TabBarStyle // Style when cursor is over the TabBar
  37. Focus TabBarStyle // Style when the TabBar has key focus
  38. Disabled TabBarStyle // Style when the TabBar is disabled
  39. Tab TabStyles // Style for Tabs
  40. }
  41. // TabStyle describes the style of the individual Tabs header
  42. type TabStyle struct {
  43. Margins BorderSizes // Tab header margins
  44. Border BorderSizes // Tab header borders
  45. Paddings BorderSizes // Tab header paddings
  46. BorderColor math32.Color4 // Tab header border color
  47. BgColor math32.Color4 // Tab header background color
  48. FgColor math32.Color // Tab header color for icon and text
  49. }
  50. // TabStyles describes all Tab styles
  51. type TabStyles struct {
  52. IconPaddings BorderSizes // Paddings for optional icon
  53. ImagePaddings BorderSizes // Paddings for optional image
  54. IconClose string // Codepoint for close icon in Tab header
  55. Normal TabStyle // Style for normal exhibition
  56. Over TabStyle // Style when cursor is over the Tab
  57. Focus TabStyle // Style when the Tab has key focus
  58. Disabled TabStyle // Style when the Tab is disabled
  59. Selected TabStyle // Style when the Tab is selected
  60. }
  61. // NewTabBar creates and returns a pointer to a new TabBar widget
  62. // with the specified width and height
  63. func NewTabBar(width, height float32) *TabBar {
  64. // Creates new TabBar
  65. tb := new(TabBar)
  66. tb.Initialize(width, height)
  67. tb.styles = &StyleDefault().TabBar
  68. tb.tabs = make([]*Tab, 0)
  69. tb.selected = -1
  70. // Creates separator panel (between the tab headers and content panel)
  71. tb.separator.Initialize(0, 0)
  72. tb.Add(&tb.separator)
  73. // Create list for contained tabs not visible
  74. tb.list = NewVList(0, 0)
  75. tb.list.Subscribe(OnMouseOut, func(evname string, ev interface{}) {
  76. tb.list.SetVisible(false)
  77. })
  78. tb.list.Subscribe(OnChange, tb.onListChange)
  79. tb.Add(tb.list)
  80. // Creates list icon button
  81. tb.listButton = NewLabel(tb.styles.ListButtonIcon, true)
  82. tb.listButton.SetPaddingsFrom(&tb.styles.ListButtonPaddings)
  83. tb.listButton.Subscribe(OnMouseDown, tb.onListButton)
  84. tb.Add(tb.listButton)
  85. // Subscribe to panel events
  86. tb.Subscribe(OnCursorEnter, tb.onCursor)
  87. tb.Subscribe(OnCursorLeave, tb.onCursor)
  88. tb.Subscribe(OnEnable, func(name string, ev interface{}) { tb.update() })
  89. tb.Subscribe(OnResize, func(name string, ev interface{}) { tb.recalc() })
  90. tb.recalc()
  91. tb.update()
  92. return tb
  93. }
  94. // AddTab creates and adds a new Tab panel with the specified header text
  95. // at the end of this TabBar list of tabs.
  96. // Returns the pointer to thew new Tab.
  97. func (tb *TabBar) AddTab(text string) *Tab {
  98. tab := tb.InsertTab(text, len(tb.tabs))
  99. tb.SetSelected(len(tb.tabs) - 1)
  100. return tab
  101. }
  102. // InsertTab creates and inserts a new Tab panel with the specified header text
  103. // at the specified position in the TabBar from left to right.
  104. // Returns the pointer to the new Tab or nil if the position is invalid.
  105. func (tb *TabBar) InsertTab(text string, pos int) *Tab {
  106. // Checks position to insert into
  107. if pos < 0 || pos > len(tb.tabs) {
  108. return nil
  109. }
  110. // Inserts created Tab at the specified position
  111. tab := newTab(text, tb, &tb.styles.Tab)
  112. tb.tabs = append(tb.tabs, nil)
  113. copy(tb.tabs[pos+1:], tb.tabs[pos:])
  114. tb.tabs[pos] = tab
  115. tb.Add(&tab.header)
  116. tb.update()
  117. tb.recalc()
  118. return tab
  119. }
  120. // RemoveTab removes the tab at the specified position in the TabBar.
  121. // Returns an error if the position is invalid.
  122. func (tb *TabBar) RemoveTab(pos int) error {
  123. // Check position to remove from
  124. if pos < 0 || pos >= len(tb.tabs) {
  125. return fmt.Errorf("Invalid tab position:%d", pos)
  126. }
  127. // Remove tab from TabBar panel
  128. tab := tb.tabs[pos]
  129. tb.Remove(&tab.header)
  130. if tab.content != nil {
  131. tb.Remove(tab.content)
  132. }
  133. // Remove tab from tabbar array
  134. copy(tb.tabs[pos:], tb.tabs[pos+1:])
  135. tb.tabs[len(tb.tabs)-1] = nil
  136. tb.tabs = tb.tabs[:len(tb.tabs)-1]
  137. // If removed tab was selected, selects other tab.
  138. if tb.selected == pos {
  139. // Try to select tab at right
  140. if len(tb.tabs) > pos {
  141. tb.tabs[pos].setSelected(true)
  142. // Otherwise select tab at left
  143. } else if pos > 0 {
  144. tb.tabs[pos-1].setSelected(true)
  145. }
  146. }
  147. tb.update()
  148. tb.recalc()
  149. return nil
  150. }
  151. // MoveTab moves a Tab to another position in the Tabs list
  152. func (tb *TabBar) MoveTab(src, dest int) error {
  153. // Check source position
  154. if src < 0 || src >= len(tb.tabs) {
  155. return fmt.Errorf("Invalid tab source position:%d", src)
  156. }
  157. // Check destination position
  158. if dest < 0 || dest >= len(tb.tabs) {
  159. return fmt.Errorf("Invalid tab destination position:%d", dest)
  160. }
  161. if src == dest {
  162. return nil
  163. }
  164. tabDest := tb.tabs[dest]
  165. tb.tabs[dest] = tb.tabs[src]
  166. tb.tabs[src] = tabDest
  167. tb.recalc()
  168. return nil
  169. }
  170. // TabCount returns the current number of Tabs in the TabBar
  171. func (tb *TabBar) TabCount() int {
  172. return len(tb.tabs)
  173. }
  174. // TabAt returns the pointer of the Tab object at the specified position.
  175. // Return nil if the position is invalid
  176. func (tb *TabBar) TabAt(pos int) *Tab {
  177. if pos < 0 || pos >= len(tb.tabs) {
  178. return nil
  179. }
  180. return tb.tabs[pos]
  181. }
  182. // TabPosition returns the position of the Tab specified by its pointer
  183. func (tb *TabBar) TabPosition(tab *Tab) int {
  184. for i := 0; i < len(tb.tabs); i++ {
  185. if tb.tabs[i] == tab {
  186. return i
  187. }
  188. }
  189. return -1
  190. }
  191. // SetSelected sets the selected tab of the TabBar to the tab with the specified position.
  192. // Returns the pointer of the selected tab or nil if the position is invalid.
  193. func (tb *TabBar) SetSelected(pos int) *Tab {
  194. if pos < 0 || pos >= len(tb.tabs) {
  195. return nil
  196. }
  197. for i := 0; i < len(tb.tabs); i++ {
  198. if i == pos {
  199. tb.tabs[i].setSelected(true)
  200. } else {
  201. tb.tabs[i].setSelected(false)
  202. }
  203. }
  204. tb.selected = pos
  205. return tb.tabs[pos]
  206. }
  207. // Selected returns the position of the selected Tab.
  208. // Returns value < 0 if there is no selected Tab.
  209. func (tb *TabBar) Selected() int {
  210. return tb.selected
  211. }
  212. // onCursor process subscribed cursor events
  213. func (tb *TabBar) onCursor(evname string, ev interface{}) {
  214. switch evname {
  215. case OnCursorEnter:
  216. tb.cursorOver = true
  217. tb.update()
  218. case OnCursorLeave:
  219. tb.cursorOver = false
  220. tb.update()
  221. default:
  222. return
  223. }
  224. tb.root.StopPropagation(StopAll)
  225. }
  226. // onListButtonMouse process subscribed MouseButton events over the list button
  227. func (tb *TabBar) onListButton(evname string, ev interface{}) {
  228. switch evname {
  229. case OnMouseDown:
  230. if !tb.list.Visible() {
  231. tb.list.SetVisible(true)
  232. }
  233. default:
  234. return
  235. }
  236. tb.root.StopPropagation(StopAll)
  237. }
  238. // onListChange process OnChange event from the tab list
  239. func (tb *TabBar) onListChange(evname string, ev interface{}) {
  240. selected := tb.list.Selected()
  241. pos := selected[0].GetPanel().UserData().(int)
  242. log.Error("onListChange:%v", pos)
  243. tb.SetSelected(pos)
  244. tb.list.SetVisible(false)
  245. }
  246. // applyStyle applies the specified TabBar style
  247. func (tb *TabBar) applyStyle(s *TabBarStyle) {
  248. tb.SetBordersFrom(&s.Border)
  249. tb.SetBordersColor4(&s.BorderColor)
  250. tb.SetPaddingsFrom(&s.Paddings)
  251. tb.SetColor4(&s.BgColor)
  252. tb.separator.SetColor4(&s.BorderColor)
  253. }
  254. // recalc recalculates and updates the positions of all tabs
  255. func (tb *TabBar) recalc() {
  256. // Determines how many tabs could be fully shown
  257. iconWidth := tb.listButton.Width()
  258. availWidth := tb.ContentWidth() - iconWidth
  259. var tabWidth float32
  260. var totalWidth float32
  261. var count int
  262. for i := 0; i < len(tb.tabs); i++ {
  263. tab := tb.tabs[i]
  264. minw := tab.minWidth()
  265. if minw > tabWidth {
  266. tabWidth = minw
  267. }
  268. totalWidth = float32(count+1) * tabWidth
  269. if totalWidth > availWidth {
  270. break
  271. }
  272. count++
  273. }
  274. // If there are more Tabs that can be shown, shows list button
  275. if count < len(tb.tabs) {
  276. // Sets the list button visible
  277. tb.listButton.SetVisible(true)
  278. height := tb.tabs[0].header.Height()
  279. iy := (height - tb.listButton.Height()) / 2
  280. tb.listButton.SetPosition(availWidth, iy)
  281. // Sets the tab list position and size
  282. listWidth := float32(200)
  283. lx := tb.ContentWidth() - listWidth
  284. ly := height + 1
  285. tb.list.SetPosition(lx, ly)
  286. tb.list.SetSize(listWidth, 200)
  287. tb.SetTopChild(tb.list)
  288. } else {
  289. tb.listButton.SetVisible(false)
  290. tb.list.SetVisible(false)
  291. }
  292. tb.list.Clear()
  293. var headerx float32
  294. // When there is available space limits the with of the tabs
  295. maxTabWidth := availWidth / float32(count)
  296. if tabWidth < maxTabWidth {
  297. tabWidth += (maxTabWidth - tabWidth) / 4
  298. }
  299. for i := 0; i < len(tb.tabs); i++ {
  300. tab := tb.tabs[i]
  301. // Recalculate Tab header and sets its position
  302. tab.recalc(tabWidth)
  303. tab.header.SetPosition(headerx, 0)
  304. // Sets size and position of the Tab content panel
  305. if tab.content != nil {
  306. cpan := tab.content.GetPanel()
  307. contenty := tab.header.Height() + tb.styles.SepHeight
  308. cpan.SetWidth(tb.ContentWidth())
  309. cpan.SetHeight(tb.ContentHeight() - contenty)
  310. cpan.SetPosition(0, contenty)
  311. }
  312. headerx += tab.header.Width()
  313. // If Tab can be shown set its header visible
  314. if i < count {
  315. tab.header.SetVisible(true)
  316. // Otherwise insert tab text in List
  317. } else {
  318. tab.header.SetVisible(false)
  319. item := NewImageLabel(tab.label.Text())
  320. item.SetUserData(i)
  321. tb.list.Add(item)
  322. }
  323. }
  324. // Sets the separator size, position and visibility
  325. if len(tb.tabs) > 0 {
  326. tb.separator.SetSize(tb.ContentWidth(), tb.styles.SepHeight)
  327. tb.separator.SetPositionY(tb.tabs[0].header.Height())
  328. tb.separator.SetVisible(true)
  329. } else {
  330. tb.separator.SetVisible(false)
  331. }
  332. }
  333. // update updates the TabBar visual state
  334. func (tb *TabBar) update() {
  335. if !tb.Enabled() {
  336. tb.applyStyle(&tb.styles.Disabled)
  337. return
  338. }
  339. if tb.cursorOver {
  340. tb.applyStyle(&tb.styles.Over)
  341. return
  342. }
  343. tb.applyStyle(&tb.styles.Normal)
  344. }
  345. //
  346. // Tab describes an individual tab of the TabBar
  347. //
  348. type Tab struct {
  349. tb *TabBar // Pointer to parent *TabBar
  350. styles *TabStyles // Pointer to Tab current styles
  351. header Panel // Tab header
  352. label *Label // Tab user label
  353. iconClose *Label // Tab close icon
  354. icon *Label // Tab optional user icon
  355. image *Image // Tab optional user image
  356. bottom Panel // Panel to cover the bottom edge of the Tab
  357. content IPanel // User content panel
  358. cursorOver bool
  359. selected bool
  360. pinned bool
  361. }
  362. // newTab creates and returns a pointer to a new Tab
  363. func newTab(text string, tb *TabBar, styles *TabStyles) *Tab {
  364. tab := new(Tab)
  365. tab.tb = tb
  366. tab.styles = styles
  367. // Setup the header panel
  368. tab.header.Initialize(0, 0)
  369. tab.label = NewLabel(text)
  370. tab.iconClose = NewLabel(styles.IconClose, true)
  371. tab.header.Add(tab.label)
  372. tab.header.Add(tab.iconClose)
  373. // Creates the bottom panel
  374. tab.bottom.Initialize(0, 0)
  375. tab.bottom.SetBounded(false)
  376. tab.bottom.SetColor4(&tab.styles.Selected.BgColor)
  377. tab.header.Add(&tab.bottom)
  378. // Subscribe to header panel events
  379. tab.header.Subscribe(OnCursorEnter, tab.onCursor)
  380. tab.header.Subscribe(OnCursorLeave, tab.onCursor)
  381. tab.header.Subscribe(OnMouseDown, tab.onMouseHeader)
  382. tab.iconClose.Subscribe(OnMouseDown, tab.onMouseIcon)
  383. tab.update()
  384. return tab
  385. }
  386. // onCursor process subscribed cursor events over the tab header
  387. func (tab *Tab) onCursor(evname string, ev interface{}) {
  388. switch evname {
  389. case OnCursorEnter:
  390. tab.cursorOver = true
  391. tab.update()
  392. case OnCursorLeave:
  393. tab.cursorOver = false
  394. tab.update()
  395. default:
  396. return
  397. }
  398. tab.header.root.StopPropagation(StopAll)
  399. }
  400. // onMouse process subscribed mouse events over the tab header
  401. func (tab *Tab) onMouseHeader(evname string, ev interface{}) {
  402. switch evname {
  403. case OnMouseDown:
  404. mev := ev.(*window.MouseEvent)
  405. if mev.Button == window.MouseButtonLeft {
  406. tab.tb.SetSelected(tab.tb.TabPosition(tab))
  407. } else {
  408. tab.header.Dispatch(OnRightClick, ev)
  409. }
  410. default:
  411. return
  412. }
  413. tab.header.root.StopPropagation(StopAll)
  414. }
  415. // onMouseIcon process subscribed mouse events over the tab close icon
  416. func (tab *Tab) onMouseIcon(evname string, ev interface{}) {
  417. switch evname {
  418. case OnMouseDown:
  419. tab.tb.RemoveTab(tab.tb.TabPosition(tab))
  420. default:
  421. return
  422. }
  423. tab.header.root.StopPropagation(StopAll)
  424. }
  425. // SetText sets the text of the tab header
  426. func (tab *Tab) SetText(text string) *Tab {
  427. tab.label.SetText(text)
  428. // Needs to recalculate all Tabs because this Tab width will change
  429. tab.tb.recalc()
  430. return tab
  431. }
  432. // SetIcon sets the optional icon of the Tab header
  433. func (tab *Tab) SetIcon(icon string) *Tab {
  434. // Remove previous header image if any
  435. if tab.image != nil {
  436. tab.header.Remove(tab.image)
  437. tab.image.Dispose()
  438. tab.image = nil
  439. }
  440. // Creates or updates icon
  441. if tab.icon == nil {
  442. tab.icon = NewLabel(icon, true)
  443. tab.icon.SetPaddingsFrom(&tab.styles.IconPaddings)
  444. tab.header.Add(tab.icon)
  445. } else {
  446. tab.icon.SetText(icon)
  447. }
  448. // Needs to recalculate all Tabs because this Tab width will change
  449. tab.tb.recalc()
  450. return tab
  451. }
  452. // SetImage sets the optional image of the Tab header
  453. func (tab *Tab) SetImage(imgfile string) error {
  454. // Remove previous icon if any
  455. if tab.icon != nil {
  456. tab.header.Remove(tab.icon)
  457. tab.icon.Dispose()
  458. tab.icon = nil
  459. }
  460. // Creates or updates image
  461. if tab.image == nil {
  462. // Creates image panel from file
  463. img, err := NewImage(imgfile)
  464. if err != nil {
  465. return err
  466. }
  467. tab.image = img
  468. tab.image.SetPaddingsFrom(&tab.styles.ImagePaddings)
  469. tab.header.Add(tab.image)
  470. } else {
  471. err := tab.image.SetImage(imgfile)
  472. if err != nil {
  473. return err
  474. }
  475. }
  476. // Scale image so its height is not greater than the Label height
  477. if tab.image.Height() > tab.label.Height() {
  478. tab.image.SetContentAspectHeight(tab.label.Height())
  479. }
  480. // Needs to recalculate all Tabs because this Tab width will change
  481. tab.tb.recalc()
  482. return nil
  483. }
  484. // SetPinned sets the tab pinned state.
  485. // A pinned tab cannot be removed by the user because the close icon is not shown.
  486. func (tab *Tab) SetPinned(pinned bool) {
  487. tab.pinned = pinned
  488. tab.iconClose.SetVisible(!pinned)
  489. }
  490. // Pinned returns this tab pinned state
  491. func (tab *Tab) Pinned() bool {
  492. return tab.pinned
  493. }
  494. // Header returns a pointer to this Tab header panel.
  495. // Can be used to set an event handler when the Tab header is right clicked.
  496. // (to show a context Menu for example).
  497. func (tab *Tab) Header() *Panel {
  498. return &tab.header
  499. }
  500. // SetContent sets or replaces this tab content panel.
  501. func (tab *Tab) SetContent(ipan IPanel) {
  502. // Remove previous content if any
  503. if tab.content != nil {
  504. tab.tb.Remove(tab.content)
  505. }
  506. tab.content = ipan
  507. if ipan != nil {
  508. tab.tb.Add(tab.content)
  509. }
  510. tab.tb.recalc()
  511. }
  512. // Content returns a pointer to the specified Tab content panel
  513. func (tab *Tab) Content() IPanel {
  514. return tab.content
  515. }
  516. // setSelected sets this Tab selected state
  517. func (tab *Tab) setSelected(selected bool) {
  518. tab.selected = selected
  519. if tab.content != nil {
  520. tab.content.GetPanel().SetVisible(selected)
  521. }
  522. tab.bottom.SetVisible(selected)
  523. tab.update()
  524. tab.setBottomPanel()
  525. }
  526. // minWidth returns the minimum width of this Tab header to allow
  527. // all of its elements to be shown in full.
  528. func (tab *Tab) minWidth() float32 {
  529. var minWidth float32
  530. if tab.icon != nil {
  531. minWidth = tab.icon.Width()
  532. } else if tab.image != nil {
  533. minWidth = tab.image.Width()
  534. }
  535. minWidth += tab.label.Width()
  536. minWidth += tab.iconClose.Width()
  537. return minWidth + tab.header.MinWidth()
  538. }
  539. // applyStyle applies the specified Tab style to the Tab header
  540. func (tab *Tab) applyStyle(s *TabStyle) {
  541. tab.header.SetMarginsFrom(&s.Margins)
  542. tab.header.SetBordersFrom(&s.Border)
  543. tab.header.SetBordersColor4(&s.BorderColor)
  544. tab.header.SetPaddingsFrom(&s.Paddings)
  545. tab.header.SetColor4(&s.BgColor)
  546. }
  547. // update updates the Tab header visual style
  548. func (tab *Tab) update() {
  549. if !tab.header.Enabled() {
  550. tab.applyStyle(&tab.styles.Disabled)
  551. return
  552. }
  553. if tab.selected {
  554. tab.applyStyle(&tab.styles.Selected)
  555. return
  556. }
  557. if tab.cursorOver {
  558. tab.applyStyle(&tab.styles.Over)
  559. return
  560. }
  561. tab.applyStyle(&tab.styles.Normal)
  562. }
  563. // setBottomPanel sets the position and size of the Tab bottom panel
  564. // to cover the Tabs separator
  565. func (tab *Tab) setBottomPanel() {
  566. if tab.selected {
  567. bwidth := tab.header.ContentWidth() + tab.header.Paddings().Left + tab.header.Paddings().Right
  568. bx := tab.styles.Selected.Margins.Left + tab.styles.Selected.Border.Left
  569. tab.bottom.SetSize(bwidth, tab.tb.styles.SepHeight)
  570. tab.bottom.SetPosition(bx, tab.header.Height())
  571. }
  572. }
  573. // recalc recalculates the size of the Tab header and the size
  574. // and positions of the Tab header internal panels
  575. func (tab *Tab) recalc(width float32) {
  576. height := tab.label.Height()
  577. tab.header.SetContentHeight(height)
  578. tab.header.SetWidth(width)
  579. labx := float32(0)
  580. if tab.icon != nil {
  581. icy := (tab.header.ContentHeight() - tab.icon.Height()) / 2
  582. tab.icon.SetPosition(0, icy)
  583. labx = tab.icon.Width()
  584. } else if tab.image != nil {
  585. tab.image.SetPosition(0, 0)
  586. labx = tab.image.Width()
  587. }
  588. tab.label.SetPosition(labx, 0)
  589. // Sets the close icon position
  590. icx := tab.header.ContentWidth() - tab.iconClose.Width()
  591. icy := (tab.header.ContentHeight() - tab.iconClose.Height()) / 2
  592. tab.iconClose.SetPosition(icx, icy)
  593. // Sets the position of the bottom panel to cover separator
  594. tab.setBottomPanel()
  595. }