Fantom

 

//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   9 Sep 08  Andy Frank  Creation
//

using gfx
using fwt

**
** ViewTabPane manages ViewTabs.
**
internal class ViewTabPane : Pane
{

  **
  ** Construct with one default tab.
  **
  new make(Frame frame)
  {
    this.frame = frame
    this.tabs = [ ViewTab(frame) ]
    this.active = tabs[0]

    add(tbar = TabBar(this))
    add(active)
  }

  **
  ** Get a listing of all the tabs mapped to views.
  **
  View[] views()
  {
    tabs.map |ViewTab t->View| { t.view }
  }

  **
  ** Create a new tab.  The new tab is not selected.  It
  ** is up the caller to select it once loading is complete.
  **
  ViewTab newTab()
  {
    tab := ViewTab(frame)
    add(tab)
    tabs.add(tab)
    select(tab)
    return tab
  }

  **
  ** Close the specified view tab.
  **
  Void close(ViewTab tab)
  {
    if (!tab.confirmClose) return
    ViewTab? newActive := null
    if (tab === active)
    {
      i := tabs.index(tab)
      if (i == 0) newActive = tabs[1]
      else if (i == tabs.size-1) newActive = tabs[-2]
      else newActive = tabs[i+1]
    }
    remove(tab)
    tabs.remove(tab)
    if (newActive != null) select(newActive)
    else relayout
  }

  **
  ** Select the specified view tab as the new active tab.
  **
  Void select(ViewTab tab)
  {
    onSelect(Event { data=tab })
  }

  **
  ** Handle new tab selection
  **
  Void onSelect(Event event)
  {
    oldActive := this.active
    this.active = event.data
    if (active === oldActive) return
    oldActive.deactivate
    active.activate
    oldActive.visible = false
    active.visible = true
    relayout
  }

  **
  ** Use pref size
  **
  override Size prefSize(Hints hints := Hints.defVal)
  {
    return Size(100, 100)
  }

  **
  ** Layout widget.
  **
  override Void onLayout()
  {
    th := 0

    if (tabs.size == 1)
    {
      tbar.bounds = Rect(0,0,0,0)
    }
    else
    {
      th = tbar.prefSize.h
      tbar.bounds = Rect(0, 0, size.w, th)
      tbar.relayout
      tbar.repaint
    }

    active.bounds = Rect(0, th, size.w, size.h-th)
    active.relayout
  }

  internal Frame frame { private set }
  internal ViewTab active { private set }
  internal ViewTab[] tabs { private set }
  private TabBar tbar
}

**************************************************************************
** TabBar
**************************************************************************
internal class TabBar : Canvas
{
  new make(ViewTabPane pane)
  {
    this.pane = pane
    onMouseDown.add { pressed(it) }
  }

  override Size prefSize(Hints hints := Hints.defVal)
  {
    ph := tabInsets.top + 16.max(fontActive.height) + tabInsets.bottom
    return Size(100, ph)
  }

  const Gradient bgActive   := Gradient("0% 0%, 0% 100%, $Desktop.sysLightShadow, $Desktop.sysBg")
  const Gradient bgInactive := Gradient("0% 0%, 0% 100%, $Desktop.sysBg, $Desktop.sysNormShadow")

  override Void onPaint(Graphics g)
  {
    w  := size.w
    h  := size.h
    tx := 0

    outline := Desktop.sysNormShadow

    tabBounds.clear

    pane.tabs.each |ViewTab tab|
    {
      icon := tab.image
      text := tab.text

      active := tab === pane.active
      font   := active ? fontActive : fontInactive
      bg     := active ? bgActive : bgInactive

      // use active font for layout to keep width consistent
      iw := icon.size.w
      cw := iconClose.size.w
      tw := fontActive.width(text) + tabInsets.left + tabInsets.right + iw + iconGap + cw + iconGap
      th := prefSize.h
      ty := h - th
      ix := tx + tabInsets.left
      iy := (th - icon.size.h) / 2
      lx := ix + iw + iconGap
      ly := (th - font.height) / 2
      cx := tx + tw - tabInsets.right - cw
      cy := (th - iconClose.size.h) / 2

      g.brush = bg
      g.fillRect(tx, ty, tw, th)

      g.brush = outline
      g.drawLine(tx,    ty, tx,    th)
      g.drawLine(tx,    ty, tx+tw, ty)
      g.drawLine(tx+tw, ty, tx+tw, th)

      g.font = font
      g.brush = Desktop.sysFg
      g.drawImage(icon, ix, iy)
      g.drawText(text, lx, ly)
      g.drawImage(iconClose, cx, cy)

      g.brush = outline
      g.drawLine(0, th-1, w-1, th-1)

      tabBounds.add(Rect(tx,ty,tw,th))
      tx += tw
    }
  }

  Void pressed(Event event)
  {
    if (event.id == EventId.mouseDown && event.button == 1)
    {
      close := false
      Int? tab := tabBounds.eachWhile |Rect r, Int i->Int?|
      {
        if (r.contains(event.pos.x, event.pos.y))
        {
          if (event.pos.x > r.x + r.w - tabInsets.right - iconClose.size.w)
            close = true
          return i
        }
        return null
      }

      if (tab != null)
      {
        t := pane.tabs[tab]
        if (close) pane.close(t)
        else pane.select(t)
      }
    }
  }

  ViewTabPane pane
  Rect[] tabBounds := Rect[,]
  Image iconClose  := Flux.icon(`/x16/close.png`)

  const Int iconGap       := 3
  const Insets tabInsets  := Insets(5,5,5,5)
  const Font fontActive   := Desktop.sysFont.toBold
  const Font fontInactive := Desktop.sysFont
}