//
// Copyright (c) 2012, Brian Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 2 Jun 12 Brian Frank Creation
//
using gfx
using fwt
**
** Panel is the fundamental scrollable pane used to contain
** an Editor and ItemLists
**
abstract class Panel : Canvas
{
//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////
new make()
{
this.doubleBuffered = true
onMouseDown.add |e| { mouseDown(e) }
onMouseMove.add |e| { mouseMove(e) }
onMouseUp.add |e| { mouseUp(e) }
onMouseWheel.add |e| { mouseWheel(e) }
}
//////////////////////////////////////////////////////////////////////////
// Overrides
//////////////////////////////////////////////////////////////////////////
** Total number of lines
abstract Int lineCount()
** Height of each line
abstract Int lineh()
** Total number of columns
abstract Int colCount()
** Width of each column
abstract Int colw()
** Get inclusive range of lines current in viewport
Range viewportLines()
{
startLine .. startLine.plus(visibleLines).min(lineCount-1)
}
** Get inclusive range of columns current in viewport
Range viewportCols()
{
startCol .. startCol.plus(visibleCols).min(colCount-1)
}
** Scroll so given line is top of viewport
Void scrollToLine(Int startLine)
{
this.startLine = startLine
lastSize = Size.defVal
repaint
}
** Scroll so given column is top of viewport
Void scrollToCol(Int startCol)
{
this.startCol = startCol
lastSize = Size.defVal
repaint
}
//////////////////////////////////////////////////////////////////////////
// Point <-> Position
//////////////////////////////////////////////////////////////////////////
** Point position to logical line
Int yToLine(Int y) { startLine + (y-margin.top)/lineh }
** Point position to logical columns
Int xToCol(Int x) { startCol + (x-margin.left)/colw}
** Convert logical column to x coordinate
Int colToX(Int col) { margin.left + (col - startCol) * colw }
** Convert logical line to y coordinate
Int lineToY(Int line) { margin.top + (line - startLine) * lineh }
//////////////////////////////////////////////////////////////////////////
// Layout
//////////////////////////////////////////////////////////////////////////
override Size prefSize(Hints hints := Hints.defVal)
{
return Size(200, 200)
}
private Void doLayout()
{
// compute easy stuff
numLines := this.lineCount
numCols := this.colCount
this.visibleLines = (((size.h - margin.toSize.h) / lineh)).min(numLines)
this.visibleCols = (size.w - margin.toSize.w) / colw
// check if startLine needs adjusting
maxStartLine := (numLines - visibleLines).max(0)
if (startLine >= maxStartLine) this.startLine = maxStartLine
if (startLine >= numLines) startLine = numLines - 1
if (visibleLines >= numLines) startLine = 0
if (startLine < 0) startLine = 0
// now we know end line
this.endLine = startLine + visibleLines
if (endLine >= numLines) endLine = numLines - 1
// check if startCol needs adjusting
maxStartCol := (numCols - visibleCols + 1).max(0)
if (startCol >= maxStartCol) this.startCol = maxStartCol
if (startCol < 0) startCol = 0
// now we know end col
this.endCol = startCol + visibleCols
if (endCol >= numCols) endCol = numCols - 1
// compute/limit size of vertical thumb
if (visibleLines >= numLines) this.vthumb = null
else
{
vthumb1 := (startLine.toFloat / numLines.toFloat * size.h).toInt
vthumb2 := ((startLine + visibleLines).toFloat / numLines.toFloat * size.h).toInt
vthumbh := vthumb2 - vthumb1
if (vthumbh < thumbMin) vthumbh = thumbMin
if (vthumb1 + vthumbh >= size.h) vthumb1 = size.h - vthumbh - 1
this.vthumb = Rect(size.w - vthumbw-1, vthumb1, vthumbw, vthumbh)
}
// compute/limit size of horizontal thumb
if (visibleCols >= numCols) this.hthumb = null
else
{
hthumb1 := (startCol.toFloat / numCols.toFloat * size.w).toInt
hthumb2 := (endCol.toFloat / numCols.toFloat * size.w).toInt
hthumbw := hthumb2 - hthumb1
if (hthumbw < thumbMin) hthumbw = thumbMin
if (hthumb1 + hthumbw >= size.w) hthumb1 = size.w - hthumbw - 1
this.hthumb = Rect(hthumb1, size.h - hthumbh, hthumbw, hthumbh)
}
}
//////////////////////////////////////////////////////////////////////////
// Painting
//////////////////////////////////////////////////////////////////////////
override final Void onPaint(Graphics g)
{
if (lastSize != this.size || lastLineCount != this.lineCount)
{
lastLineCount = lineCount
lastSize = size
doLayout
}
// background
g.antialias = true
w := size.w; h := size.h
g.brush = wallpaperColor
g.fillRect(0, 0, w, h)
g.brush = viewportColor
g.fillRoundRect(0, 0, w-1, h-1, 10, 10)
onPaintBackground(g)
// viewport
if (lineCount > 0)
{
g.push
g.translate(margin.left - startCol*colw, margin.top)
onPaintLines(g, viewportLines)
g.pop
}
// vertical scrollbar gutters
if (vthumb != null)
{
g.brush = gutterColor
g.fillRoundRect(vthumb.x, 0, vthumb.w, size.h-1, 10, 10)
g.fillRect(vthumb.x, 0, 5, size.h-1)
g.brush = gutterBorder
g.drawLine(vthumb.x, 0, vthumb.x, size.h)
}
// horizontal scrollbar gutter
if (hthumb != null)
{
g.brush = gutterColor
g.fillRect(1, hthumb.y, size.w-2, hthumb.h)
g.brush = gutterBorder
g.drawLine(1, hthumb.y, size.w-2, hthumb.y)
}
// border corners
g.brush = borderColor
g.drawRoundRect(0, 0, w-1, h-1, 10, 10)
// vertical thumb
if (vthumb != null)
{
g.brush = thumbColor
g.fillRoundRect(vthumb.x, vthumb.y, vthumb.w, vthumb.h, 10, 10)
}
// hozitonal thumb
if (hthumb != null)
{
g.brush = thumbColor
g.fillRoundRect(hthumb.x, hthumb.y, hthumb.w, hthumb.h, 10, 10)
}
}
**
** Paint the background
**
virtual Void onPaintBackground(Graphics g) {}
**
** Paint the viewport's given range of lines. The viewport
** is already translated for scroll position so that 0,0 represents
** the top, left corner of current scroll line/col.
**
abstract Void onPaintLines(Graphics g, Range lines)
//////////////////////////////////////////////////////////////////////////
// Eventing
//////////////////////////////////////////////////////////////////////////
private Void mouseDown(Event e)
{
pos := e.pos
if (vthumb != null && vthumb.contains(pos.x, pos.y))
{
e.consume
vdrag = pos.y - vthumb.y
return
}
if (hthumb != null && hthumb.contains(pos.x, pos.y))
{
e.consume
hdrag = pos.x - hthumb.x
return
}
}
private Void mouseMove(Event e)
{
pos := e.pos
if (vdrag != null)
{
e.consume
thumby := pos.y - vdrag
line := ((thumby.toFloat / size.h.toFloat) * lineCount.toFloat).toInt
scrollToLine(line)
}
if (hdrag != null)
{
e.consume
thumbx := pos.x - hdrag
col := ((thumbx.toFloat / size.w.toFloat) * colCount.toFloat).toInt
scrollToCol(col)
}
}
private Void mouseUp(Event e)
{
if (vdrag != null) { e.consume; vdrag = null }
if (hdrag != null) { e.consume; hdrag = null }
}
internal Void mouseWheel(Event e)
{
if (e.delta.y != 0)
{
e.consume
scrollToLine(startLine + e.delta.y)
}
}
//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////
Color wallpaperColor := Desktop.sysBg
Color viewportColor := Color.white
Color borderColor := Color(0x88_88_88)
Color gutterColor := Color(0xee_ee_ee)
Color gutterBorder := Color(0xdd_dd_dd)
Color thumbColor := Color(0xbb_bb_bb)
Insets margin := Insets(6, 6, 6, 6)
Int vthumbw := 9
Int hthumbh := 9
Int thumbMin := 30
private Size lastSize := Size.defVal // check for layout
private Int lastLineCount // last number of lines
private Int startLine // line at top of viewport
private Int endLine // line at bottom of viewport
private Int startCol // column at left of viewport
private Int endCol // column at right of viewport
private Int visibleLines // num viewable lines
private Int visibleCols // num viewable columns
private Rect? vthumb // vertical thumb
private Rect? hthumb // horizontal thumb
private Int? vdrag // if drag in progress
private Int? hdrag // if drag in progress
}