tabbar.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  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. )
  9. // TabBar is a panel which can contain other panels arranged in horizontal Tabs.
  10. // Only one panel is visible at a time.
  11. // To show another panel the corresponding Tab must be selected.
  12. type TabBar struct {
  13. Panel // Embedded panel
  14. styles *TabBarStyles // Pointer to current styles
  15. tabs []*Tab // Array of tabs
  16. separator Panel // Separator Panel
  17. iconList *Label // Icon for tab list button
  18. list *List // List for not visible tabs
  19. selected int // Index of the selected tab
  20. cursorOver bool // Cursor over TabBar panel flag
  21. }
  22. // TabBarStyle describes the style of the TabBar
  23. type TabBarStyle struct {
  24. Border BorderSizes // Border sizes
  25. Paddings BorderSizes // Padding sizes
  26. BorderColor math32.Color4 // Border color
  27. BgColor math32.Color4 // Background color
  28. }
  29. // TabBarStyles describes all the TabBarStyles
  30. type TabBarStyles struct {
  31. SepHeight float32 // Separator width
  32. IconList string // Icon for showing tab list
  33. IconPaddings BorderSizes
  34. Normal TabBarStyle // Style for normal exhibition
  35. Over TabBarStyle // Style when cursor is over the TabBar
  36. Focus TabBarStyle // Style when the TabBar has key focus
  37. Disabled TabBarStyle // Style when the TabBar is disabled
  38. Tab TabStyles // Style for Tabs
  39. }
  40. // TabStyle describes the style of the individual Tabs
  41. type TabStyle struct {
  42. Margins BorderSizes
  43. Border BorderSizes
  44. Paddings BorderSizes
  45. BorderColor math32.Color4
  46. BgColor math32.Color4
  47. FgColor math32.Color
  48. }
  49. // TabStyles describes all Tab styles
  50. type TabStyles struct {
  51. IconClose string // Codepoint for close icon in Tab header
  52. Normal TabStyle // Style for normal exhibition
  53. Over TabStyle // Style when cursor is over the Tab
  54. Focus TabStyle // Style when the Tab has key focus
  55. Disabled TabStyle // Style when the Tab is disabled
  56. Selected TabStyle // Style when the Tab is selected
  57. }
  58. // NewTabBar creates and returns a pointer to a new TabBar widget
  59. // with the specified width and height
  60. func NewTabBar(width, height float32) *TabBar {
  61. // Creates new TabBar
  62. tb := new(TabBar)
  63. tb.Initialize(width, height)
  64. tb.styles = &StyleDefault().TabBar
  65. tb.tabs = make([]*Tab, 0)
  66. tb.selected = -1
  67. // Creates separator panel
  68. tb.separator.Initialize(0, 0)
  69. tb.Add(&tb.separator)
  70. // Create list
  71. tb.list = NewVList(0, 0)
  72. tb.list.Subscribe(OnMouseOut, func(evname string, ev interface{}) {
  73. tb.list.SetVisible(false)
  74. })
  75. tb.list.Subscribe(OnChange, tb.onListChange)
  76. tb.Add(tb.list)
  77. // Creates list icon button
  78. tb.iconList = NewLabel(tb.styles.IconList, true)
  79. tb.iconList.SetPaddingsFrom(&tb.styles.IconPaddings)
  80. tb.iconList.Subscribe(OnMouseDown, tb.onListButton)
  81. tb.Add(tb.iconList)
  82. // Subscribe to panel events
  83. tb.Subscribe(OnCursorEnter, tb.onCursor)
  84. tb.Subscribe(OnCursorLeave, tb.onCursor)
  85. tb.Subscribe(OnEnable, func(name string, ev interface{}) { tb.update() })
  86. tb.Subscribe(OnResize, func(name string, ev interface{}) { tb.recalc() })
  87. tb.recalc()
  88. tb.update()
  89. return tb
  90. }
  91. // AddTab creates and adds a new Tab panel with the specified header text
  92. // at the end of this TabBar list of tabs.
  93. // Returns the pointer to thew new Tab.
  94. func (tb *TabBar) AddTab(text string) *Tab {
  95. tab := tb.InsertTab(text, len(tb.tabs))
  96. tb.SetSelected(len(tb.tabs) - 1)
  97. return tab
  98. }
  99. // InsertTab creates and inserts a new Tab panel with the specified header text
  100. // at the specified position in the TabBar from left to right.
  101. // Returns the pointer to the new Tab or nil if the position is invalid.
  102. func (tb *TabBar) InsertTab(text string, pos int) *Tab {
  103. // Checks position to insert into
  104. if pos < 0 || pos > len(tb.tabs) {
  105. return nil
  106. }
  107. // Inserts created Tab at the specified position
  108. tab := newTab(text, tb, &tb.styles.Tab)
  109. tb.tabs = append(tb.tabs, nil)
  110. copy(tb.tabs[pos+1:], tb.tabs[pos:])
  111. tb.tabs[pos] = tab
  112. tb.Add(&tab.header)
  113. tb.Add(&tab.content)
  114. tb.update()
  115. tb.recalc()
  116. return tab
  117. }
  118. // RemoveTab removes the tab at the specified position in the TabBar.
  119. // Returns an error if the position is invalid.
  120. func (tb *TabBar) RemoveTab(pos int) error {
  121. // Check position to remove from
  122. if pos < 0 || pos >= len(tb.tabs) {
  123. return fmt.Errorf("Invalid tab position:%d", pos)
  124. }
  125. // Remove tab from TabBar panel
  126. tab := tb.tabs[pos]
  127. tb.Remove(&tab.header)
  128. tb.Remove(&tab.content)
  129. // Remove tab from tabbar array
  130. copy(tb.tabs[pos:], tb.tabs[pos+1:])
  131. tb.tabs[len(tb.tabs)-1] = nil
  132. tb.tabs = tb.tabs[:len(tb.tabs)-1]
  133. // Checks if removed tab was selected
  134. if tb.selected == pos {
  135. // TODO
  136. }
  137. tb.update()
  138. tb.recalc()
  139. return nil
  140. }
  141. // TabCount returns the current number of Tabs in the TabBar
  142. func (tb *TabBar) TabCount() int {
  143. return len(tb.tabs)
  144. }
  145. // TabAt returns the pointer of the Tab object at the specified position.
  146. // Return nil if the position is invalid
  147. func (tb *TabBar) TabAt(pos int) *Tab {
  148. if pos < 0 || pos >= len(tb.tabs) {
  149. return nil
  150. }
  151. return tb.tabs[pos]
  152. }
  153. // TabPosition returns the position of the Tab specified by its pointer
  154. func (tb *TabBar) TabPosition(tab *Tab) int {
  155. for i := 0; i < len(tb.tabs); i++ {
  156. if tb.tabs[i] == tab {
  157. return i
  158. }
  159. }
  160. return -1
  161. }
  162. // SetSelected sets the selected tab of the TabBar to the tab with the specified position.
  163. // Returns the pointer of the selected tab or nil if the position is invalid.
  164. func (tb *TabBar) SetSelected(pos int) *Tab {
  165. if pos < 0 || pos >= len(tb.tabs) {
  166. return nil
  167. }
  168. for i := 0; i < len(tb.tabs); i++ {
  169. if i == pos {
  170. tb.tabs[i].setSelected(true)
  171. } else {
  172. tb.tabs[i].setSelected(false)
  173. }
  174. }
  175. tb.selected = pos
  176. return tb.tabs[pos]
  177. }
  178. // Selected returns the position of the selected Tab.
  179. // Returns value < 0 if there is no selected Tab.
  180. func (tb *TabBar) Selected() int {
  181. return tb.selected
  182. }
  183. // onCursor process subscribed cursor events
  184. func (tb *TabBar) onCursor(evname string, ev interface{}) {
  185. switch evname {
  186. case OnCursorEnter:
  187. tb.cursorOver = true
  188. tb.update()
  189. case OnCursorLeave:
  190. tb.cursorOver = false
  191. tb.update()
  192. default:
  193. return
  194. }
  195. tb.root.StopPropagation(StopAll)
  196. }
  197. // onListButtonMouse process subscribed MouseButton events over the list button
  198. func (tb *TabBar) onListButton(evname string, ev interface{}) {
  199. switch evname {
  200. case OnMouseDown:
  201. if !tb.list.Visible() {
  202. tb.list.SetVisible(true)
  203. }
  204. default:
  205. return
  206. }
  207. tb.root.StopPropagation(StopAll)
  208. }
  209. // onListChange process OnChange event from the tab list
  210. func (tb *TabBar) onListChange(evname string, ev interface{}) {
  211. selected := tb.list.Selected()
  212. pos := selected[0].GetPanel().UserData().(int)
  213. tb.SetSelected(pos)
  214. tb.list.SetVisible(false)
  215. }
  216. // applyStyle applies the specified TabBar style
  217. func (tb *TabBar) applyStyle(s *TabBarStyle) {
  218. tb.SetBordersFrom(&s.Border)
  219. tb.SetBordersColor4(&s.BorderColor)
  220. tb.SetPaddingsFrom(&s.Paddings)
  221. tb.SetColor4(&s.BgColor)
  222. tb.separator.SetColor4(&s.BorderColor)
  223. }
  224. // recalc recalculates and updates the positions of all tabs
  225. func (tb *TabBar) recalc() {
  226. // Determines how many tabs could be fully shown
  227. iconWidth := tb.iconList.Width()
  228. availWidth := tb.ContentWidth() - iconWidth
  229. var tabWidth float32
  230. var totalWidth float32
  231. var count int
  232. for i := 0; i < len(tb.tabs); i++ {
  233. tab := tb.tabs[i]
  234. minw := tab.minWidth()
  235. if minw > tabWidth {
  236. tabWidth = minw
  237. }
  238. totalWidth = float32(count+1) * tabWidth
  239. if totalWidth > availWidth {
  240. break
  241. }
  242. count++
  243. }
  244. tb.list.Clear()
  245. if count < len(tb.tabs) {
  246. // Sets the list button visible andposition
  247. tb.iconList.SetVisible(true)
  248. height := tb.tabs[0].header.Height()
  249. iy := (height - tb.iconList.Height()) / 2
  250. tb.iconList.SetPosition(availWidth, iy)
  251. // Sets the tab list position and size
  252. listWidth := float32(200)
  253. lx := tb.ContentWidth() - listWidth
  254. ly := height + 1
  255. tb.list.SetPosition(lx, ly)
  256. tb.list.SetSize(listWidth, 200)
  257. tb.SetTopChild(tb.list)
  258. } else {
  259. tb.iconList.SetVisible(false)
  260. tb.list.SetVisible(false)
  261. }
  262. var headerx float32
  263. twidth := availWidth / float32(count)
  264. for i := 0; i < len(tb.tabs); i++ {
  265. tab := tb.tabs[i]
  266. // If Tab can be shown
  267. if i < count {
  268. tab.recalc(twidth)
  269. tab.header.SetPosition(headerx, 0)
  270. // Sets size and position of the Tab content panel
  271. contentx := float32(0)
  272. contenty := tab.header.Height() + tb.styles.SepHeight
  273. tab.content.SetWidth(tb.ContentWidth())
  274. tab.content.SetHeight(tb.ContentHeight() - contenty)
  275. tab.content.SetPosition(contentx, contenty)
  276. headerx += tab.header.Width()
  277. tab.header.SetVisible(true)
  278. continue
  279. // Tab cannot be shown, insert into vertical list
  280. } else {
  281. tab.header.SetVisible(false)
  282. item := NewImageLabel(tab.label.Text())
  283. item.SetUserData(i)
  284. tb.list.Add(item)
  285. }
  286. }
  287. // Sets the separator size, position and visibility
  288. if len(tb.tabs) > 0 {
  289. tb.separator.SetSize(tb.ContentWidth(), tb.styles.SepHeight)
  290. tb.separator.SetPositionY(tb.tabs[0].header.Height())
  291. tb.separator.SetVisible(true)
  292. } else {
  293. tb.separator.SetVisible(false)
  294. }
  295. }
  296. // update updates the TabBar visual state
  297. func (tb *TabBar) update() {
  298. if !tb.Enabled() {
  299. tb.applyStyle(&tb.styles.Disabled)
  300. return
  301. }
  302. if tb.cursorOver {
  303. tb.applyStyle(&tb.styles.Over)
  304. return
  305. }
  306. tb.applyStyle(&tb.styles.Normal)
  307. }
  308. //
  309. // Tab
  310. //
  311. // Tab describes an individual tab of the TabBar
  312. type Tab struct {
  313. tb *TabBar // Pointer to parent *TabBar
  314. styles *TabStyles // Pointer to Tab current styles
  315. header Panel // Tab header
  316. label *Label // Tab user label
  317. iconClose *Label // Tab close icon
  318. icon *Label // Tab optional user icon
  319. img *Image // Tab optional user image
  320. bottom Panel
  321. content Panel // User content panel
  322. cursorOver bool
  323. selected bool
  324. }
  325. // newTab creates and returns a pointer to a new Tab
  326. func newTab(text string, tb *TabBar, styles *TabStyles) *Tab {
  327. tab := new(Tab)
  328. tab.tb = tb
  329. tab.styles = styles
  330. // Setup the header panel
  331. tab.header.Initialize(0, 0)
  332. tab.label = NewLabel(text)
  333. tab.iconClose = NewLabel(styles.IconClose, true)
  334. tab.header.Add(tab.label)
  335. tab.header.Add(tab.iconClose)
  336. // Creates the bottom panel
  337. tab.bottom.Initialize(0, 0)
  338. tab.bottom.SetBounded(false)
  339. tab.bottom.SetColor4(&tab.styles.Selected.BgColor)
  340. tab.header.Add(&tab.bottom)
  341. tab.content.Initialize(0, 0)
  342. // Subscribe to header panel events
  343. tab.header.Subscribe(OnCursorEnter, tab.onCursor)
  344. tab.header.Subscribe(OnCursorLeave, tab.onCursor)
  345. tab.header.Subscribe(OnMouseDown, tab.onMouseHeader)
  346. tab.iconClose.Subscribe(OnMouseDown, tab.onMouseIcon)
  347. tab.update()
  348. return tab
  349. }
  350. // onCursor process subscribed cursor events over the tab header
  351. func (tab *Tab) onCursor(evname string, ev interface{}) {
  352. switch evname {
  353. case OnCursorEnter:
  354. tab.cursorOver = true
  355. tab.update()
  356. case OnCursorLeave:
  357. tab.cursorOver = false
  358. tab.update()
  359. default:
  360. return
  361. }
  362. tab.header.root.StopPropagation(StopAll)
  363. }
  364. // onMouse process subscribed mouse events over the tab header
  365. func (tab *Tab) onMouseHeader(evname string, ev interface{}) {
  366. switch evname {
  367. case OnMouseDown:
  368. tab.tb.SetSelected(tab.tb.TabPosition(tab))
  369. default:
  370. return
  371. }
  372. tab.header.root.StopPropagation(StopAll)
  373. }
  374. // onMouseIcon process subscribed mouse events over the tab close icon
  375. func (tab *Tab) onMouseIcon(evname string, ev interface{}) {
  376. switch evname {
  377. case OnMouseDown:
  378. tab.tb.RemoveTab(tab.tb.TabPosition(tab))
  379. default:
  380. return
  381. }
  382. tab.header.root.StopPropagation(StopAll)
  383. }
  384. // SetText sets the text of the tab header
  385. func (tab *Tab) SetText(text string) *Tab {
  386. return tab
  387. }
  388. // SetIcon sets the icon of the tab header
  389. func (tab *Tab) SetIcon(icon string) *Tab {
  390. return tab
  391. }
  392. // Content returns a pointer to the specified Tab content panel
  393. func (tab *Tab) Content() *Panel {
  394. return &tab.content
  395. }
  396. func (tab *Tab) setSelected(selected bool) {
  397. tab.selected = selected
  398. tab.content.SetVisible(selected)
  399. tab.bottom.SetVisible(selected)
  400. tab.update()
  401. tab.setBottomPanel()
  402. }
  403. // minWidth returns the minimum width of this Tab header to allow
  404. // all of its elements to be shown in full.
  405. func (tab *Tab) minWidth() float32 {
  406. var minWidth float32
  407. if tab.icon != nil {
  408. minWidth = tab.icon.Width()
  409. } else if tab.img != nil {
  410. minWidth = tab.img.Width()
  411. }
  412. minWidth += tab.label.Width()
  413. minWidth += tab.iconClose.Width()
  414. return minWidth + tab.header.MinWidth()
  415. }
  416. // applyStyle applies the specified Tab style to the Tab header
  417. func (tab *Tab) applyStyle(s *TabStyle) {
  418. tab.header.SetMarginsFrom(&s.Margins)
  419. tab.header.SetBordersFrom(&s.Border)
  420. tab.header.SetBordersColor4(&s.BorderColor)
  421. tab.header.SetPaddingsFrom(&s.Paddings)
  422. tab.header.SetColor4(&s.BgColor)
  423. }
  424. // update updates the Tab header visual style
  425. func (tab *Tab) update() {
  426. if !tab.header.Enabled() {
  427. tab.applyStyle(&tab.styles.Disabled)
  428. return
  429. }
  430. if tab.selected {
  431. tab.applyStyle(&tab.styles.Selected)
  432. return
  433. }
  434. if tab.cursorOver {
  435. tab.applyStyle(&tab.styles.Over)
  436. return
  437. }
  438. tab.applyStyle(&tab.styles.Normal)
  439. }
  440. // setBottomPanel sets the position and size of the Tab bottom panel
  441. // to cover the Tabs separator
  442. func (tab *Tab) setBottomPanel() {
  443. if tab.selected {
  444. bwidth := tab.header.ContentWidth() + tab.header.Paddings().Left + tab.header.Paddings().Right
  445. bx := tab.styles.Selected.Margins.Left + tab.styles.Selected.Border.Left
  446. tab.bottom.SetSize(bwidth, tab.tb.styles.SepHeight)
  447. tab.bottom.SetPosition(bx, tab.header.Height())
  448. }
  449. }
  450. // recalc recalculates the size of the Tab header and the size
  451. // and positions of the Tab header internal panels
  452. func (tab *Tab) recalc(width float32) {
  453. height := tab.label.Height()
  454. tab.header.SetContentHeight(height)
  455. tab.header.SetWidth(width)
  456. labx := float32(0)
  457. if tab.icon != nil {
  458. tab.icon.SetPosition(0, 0)
  459. labx = tab.icon.Width()
  460. } else if tab.img != nil {
  461. tab.img.SetPosition(0, 0)
  462. labx = tab.img.Width()
  463. }
  464. tab.label.SetPosition(labx, 0)
  465. // Sets the close icon position
  466. icx := tab.header.ContentWidth() - tab.iconClose.Width()
  467. icy := (tab.header.ContentHeight() - tab.iconClose.Height()) / 2
  468. tab.iconClose.SetPosition(icx, icy)
  469. // Sets the position of the bottom panel to cover separator
  470. tab.setBottomPanel()
  471. }