sifflet-lib-1.0: Sifflet/UI/RPanel.hs
-- | "Rowed Panel."
-- Expandable framed panel with a two-dimensional layout
-- (rows of widgets, but not with aligned columns like in a table).
module Sifflet.UI.RPanel
(
RPanel, newRPanel, rpanelId, rpanelRoot, rpanelContent
, rpanelAddWidget, rpanelAddWidgets, rpanelNewRow
, rpanelAddRows
)
where
import Control.Monad
import Sifflet.UI.LittleGtk
import Sifflet.Util
debugTracing :: Bool
debugTracing = False
data RPanel
= RPanel {
-- Public
rpId :: String -- ^ text for the expander button
, rpRoot :: GtkFrame -- ^ use the root to add rpanel to a container
, rpContent :: [[String]] -- ^ ids of widgets added, in rows
-- Widgets that make up the RPanel
, rpFrame :: GtkFrame -- ^ frame, same as root
, rpExpander :: Expander -- ^ expander
, rpVBox :: VBox -- ^ vbox to contain the rows
, rpCurrentRow :: HBox -- ^ next element goes here if it fits
-- Geometry book-keeping
, rpCurrentRowFreeWidth :: Int -- ^ free width in current row
, rpMaxWidth :: Int -- ^ maximum row width
, rpHPad :: Int -- ^ horizontal padding
}
rpanelId :: RPanel -> String
rpanelId = rpId
rpanelRoot :: RPanel -> GtkFrame
rpanelRoot = rpRoot
rpanelContent :: RPanel -> [[String]]
rpanelContent = rpContent
newRPanel :: String -> Int -> Int -> Int -> IO RPanel
newRPanel cid hpad vpad maxWidth = do
{
frame <- frameNew -- adds a border (not labeled, since the expander is)
; expander <- expanderNew cid
; expanderSetExpanded expander True
; set frame [containerChild := expander]
; vbox <- vBoxNew False vpad -- non-homogeneous heights
; widgetSetSizeRequest vbox maxWidth (-1) -- height = don't care
; set expander [containerChild := vbox]
; hbox <- hBoxNew False hpad -- non-homogeoneous widths
; boxPackStart vbox hbox PackNatural 0
; return $ RPanel {rpId = cid
, rpRoot = frame
, rpFrame = frame
, rpExpander = expander
, rpVBox = vbox
, rpCurrentRow = hbox
, rpContent = [[]]
, rpCurrentRowFreeWidth = maxWidth - hpad
, rpMaxWidth = maxWidth - hpad
, rpHPad = hpad
}
}
-- | Given a list of (name, widget) pairs, add each of the widgets
-- and its name to the rpanel
rpanelAddWidgets :: (WidgetClass widget) =>
RPanel -> [(String, widget)] -> IO RPanel
rpanelAddWidgets rp pairs =
let addPair rp' (widgetId, widget) = rpanelAddWidget rp' widgetId widget
in foldM addPair rp pairs
-- | Add a single named widget to the RPanel
rpanelAddWidget :: (WidgetClass widget) =>
RPanel -> String -> widget -> IO RPanel
rpanelAddWidget rp widgetId widget = do
{
Requisition widgetWidth _ <- widgetSizeRequest widget
; let freeWidth = rpCurrentRowFreeWidth rp
freeWidth' = freeWidth - widgetWidth - rpHPad rp
; if freeWidth' >= 0 || freeWidth == rpMaxWidth rp
-- Either there is room enough, OR we're at the start of a row
-- so starting another won't help -- in fact it would lead to
-- infinite recursion
then do
{
let content' = insertLastLast (rpContent rp) widgetId
packMode = -- PackNatural -- to left justify
PackGrow -- to fill
-- PackRepel -- to center
; boxPackStart (rpCurrentRow rp) widget packMode 0
-- ; widgetShow widget -- do this here???
; when debugTracing $
putStr (unlines ["Adding " ++ widgetId ++
" width " ++ show widgetWidth
, "Free width = " ++ show freeWidth ++
" -> " ++ show freeWidth'
, "Content -> " ++ show content'])
; return $ rp {rpContent = content'
, rpCurrentRowFreeWidth = freeWidth'}
}
else
-- We're out of room, but not at the start of the current row,
-- so start a new row
do
{
rp' <- rpanelNewRow rp
; rpanelAddWidget rp' widgetId widget
}
}
-- | Force the RPanel to begin a new row
rpanelNewRow :: RPanel -> IO RPanel
rpanelNewRow rp = do
{
hbox <- hBoxNew False (rpHPad rp)
; boxPackStart (rpVBox rp) hbox PackNatural 0
; return $ rp {rpCurrentRow = hbox
, rpContent = insertLast (rpContent rp) []
, rpCurrentRowFreeWidth = rpMaxWidth rp}
}
-- | Given a list of lists, each sublist representing a row of widgets,
-- add the widgets to the RPanel, preserving the row structure
-- as much as possible.
-- (Row structure will be broken if any intended row is too wide.)
rpanelAddRows :: (WidgetClass widget) =>
RPanel -> [[(String, widget)]] -> IO RPanel
rpanelAddRows rp rows = foldM rpanelAddRow rp rows
-- | Add a row of widgets to an RPanel.
-- This does not start a new row before the first widget,
-- but after the last, so at the end, the current row will be empty.
rpanelAddRow :: (WidgetClass widget) =>
RPanel -> [(String, widget)] -> IO RPanel
rpanelAddRow rp row =
rpanelAddWidgets rp row >>= rpanelNewRow