I have a DrawingArea onto which I can draw using primitives such as drawRectangle and drawLine. How do I draw text onto that area? I'm most interested in something that quickly outputs a single line of text.
Graphics.UI.Gtk.Gdk.Drawable.layoutLine seems to be what I want, but it wants a Graphics.Rendering.Pango.Layout.LayoutLine as input. How do I construct that LayoutLine?
Are there better alternatives than doing it this way?
Thanks!
I don't know if you would consider using Cairo. If so, I think the function showText may be what you are looking for. Use the cairo function moveTo to move to a specific location before writing the text. This is one of the simplest working examples I can produce:
import Graphics.UI.Gtk
import Graphics.Rendering.Cairo
main :: IO ()
main = do
initGUI
window <- windowNew
drawingArea <- drawingAreaNew
containerAdd window drawingArea
drawingArea `onExpose` (\_ -> renderScene drawingArea)
window `onDestroy` mainQuit
windowSetDefaultSize window 640 480
widgetShowAll window
mainGUI
renderScene :: DrawingArea -> IO Bool
renderScene da = do
dw <- widgetGetDrawWindow da
renderWithDrawable dw $ do setSourceRGBA 0.5 0.5 0.5 1.0
moveTo 100.0 100.0
showText "HelloWorld"
return True
I found the following to be an excellent guide, even though it was not for Haskell:
http://zetcode.com/tutorials/cairographicstutorial/cairotext/
I've found the way to do this with Pango.
layout <- widgetCreateLayout drawAreaWidget stringToDraw
Then you can use this newly-created layout with functions such as drawLayout and drawLayoutWithColors.
Related
In the Haskell Gloss library, one draws text with the Text constructor of the Picture type. But how, then does one find the width (and height) of such a picture?
Here's how text is rendered in Gloss:
Text str
-> do
GL.blend $= GL.Disabled
GL.preservingMatrix $ GLUT.renderString GLUT.Roman str
GL.blend $= GL.Enabled
The important point here is that it calls renderString. Looking at the documentation for renderString, we immediately see two other useful functions: stringWidth and fontHeight. As such, you can get your width and height like this:
import Graphics.UI.GLUT.Fonts
do
width <- stringWidth Roman str
height <- fontHeight Roman
I am learning how to use the Gloss library to make some animations in Haskell.
Consider the code below which animates a circle that shrinks and expands its radius with time.
import Graphics.Gloss
import Graphics.Gloss.Interface.Pure.Game
type Radius = Float
type RealTime = Float
data World = World Radius RealTime
main :: IO ()
main
= do let initWorld = World 100.0 0
let windowSize = (800,800)
let windowPosition = (200,200)
putStrLn "Before play"
play (InWindow "Wobbling Circle" windowSize windowPosition)
white 40 initWorld renderWorld handleEvent stepWorld
putStrLn "After play"
renderWorld :: World -> Picture
renderWorld (World rad rtime ) = Pictures [ Circle rad ]
handleEvent :: Event -> World -> World
handleEvent _ = id
stepWorld :: Float -> World -> World -- wobbling circle
stepWorld _ (World _ rtime) = World (100 * sin rtime) (rtime+0.1)
I compiled this code with ghc --make -O2 -threaded Main.hs on an Ubuntu 14.04 machine.
When I run the code, the "Before play" statement is printed out and then the animation starts as expected. However, when I close the animation window, the code terminates immediately without printing the "After Play" statement. Why is that?
Presumably you are using the GLUT backend (the default). Take a look at some of the source code for gloss (exactly as it appears with comments):
instance Backend GLUTState where
initBackendState = glutStateInit
initializeBackend = initializeGLUT
-- non-freeglut doesn't like this: (\_ -> GLUT.leaveMainLoop)
exitBackend = (\_ -> System.exitWith System.ExitSuccess)
....
When it exits the main loop, gloss will call exitBackend, whatever it is. For GLUT, that simply calls System.exitWith which naturally will terminate your program. The more sensible thing to do would be to call leaveMainLoop but as the comment in the code says, implementations of glut other than freeglut don't work well with that function (why? who knows. This is what the authors of gloss claim).
Your potential solutions are to use freeglut specifically and modify the source code of gloss to change exitBackend; or use the GLFW backend, which doesn't have this problem.
I'm using cairo (specifically, the haskell bindings for it) to render a game world every tick. As it is now, I'm generating my entities by creating rotating and translating,creating a path, and filling it. I'm looking for a much more efficient way to handle doing this. What I'd ideally like to do would be to create some sort of sprite and just copy it onto the surface at the correct coordinates.
How would I go about doing this? Or, if I'm thinking of this the wrong way, how should I do it otherwise? I don't really have experience with game making or cairo and I'm just doing this for fun. Any advice is appreciated.
The clock demo includes a complete example of how to cache a cairo render. See especially the redrawStaticLayers action:
let redrawStaticLayers = do
(width, height) <- widgetGetSize window
drawWin <- widgetGetDrawWindow window
background <- createImageSurface FormatARGB32 width height
foreground <- createImageSurface FormatARGB32 width height
let clear = do
save
setOperator OperatorClear
paint
restore
renderWith background $ do
clear
drawClockBackground True width height
renderWith foreground $ do
clear
drawClockForeground True width height
writeIORef backgroundRef (Just background)
writeIORef foregroundRef (Just foreground)
I'm not so experienced in Haskell and I've just started using Gtk2Hs so this might be a silly question.
I have the following Line type defined:
type Coord = (Int,Int)
type Line = (Coord,Coord)
And I have a function which draws a list of Lines on a DrawingArea. The problem is that this function draws all the Lines at the same time but I would like to draw them one at a time with a little delay between two Lines.
render :: [Line] -> IO ()
render lines =
do initGUI
win <- windowNew
windowSetTitle win "Animation"
win `onDestroy` mainQuit
can <- drawingAreaNew
can `onSizeRequest` return (Requisition 400 400)
can `onExpose` drawCanvas lines can
but <- buttonNewWithLabel "Quit"
but `onClicked` mainQuit
hbox <- hBoxNew False 0
boxPackStart hbox but PackRepel 150
vbox <- vBoxNew False 5
containerAdd vbox can
containerAdd vbox hbox
containerAdd win vbox
widgetShowAll win
mainGUI
This function gets called when the DrawingArea is exposed:
drawCanvas :: [Line] -> DrawingArea -> event -> IO Bool
drawCanvas lines can _evt =
do dw <- widgetGetDrawWindow can
drawWindowClear dw
gc <- gcNew dw
mapM_ (\(a,b) -> drawLine dw gc a b) lines
return True
I have considered using a StateT to keep track of which Lines have yet to be drawn but I don't know how to implement the animation. Even if a call widgetShowAll every time after altering the state, the window doesn't get shown until mainGUI is called.
Is it possible to make a new thread that continuously updates the state while drawCanvas somehow takes care of the drawing? If so, could somebody please show me an example of such behavior? Or is there a better approach perhaps?
Gtk2Hs allows to set up "timers" that call functions at regular intervals. So I would do the following:
Because most of Gtk2Hs happens in the IO monad, use an IORef or an MVar to store the state of the animation and change it anywhere.
Add a call to timeoutAdd before mainGUI, like this: timeoutAdd (update can lines) 100 to set up a timer that will run every 100 milliseconds and call a new function "update" with the DrawingArea and the IORef/MVar with the animation state as parameters.
In the function "update" change the animation state and call widgetQueueDraw can so that the drawing area is re-exposed. That will call "drawCanvas" automatically because it is connected to the expose event. "update" has to return an IO Bool. Returning False will stop the timer.
To update the animation state, I would use a tuple. The first element would store the line to be drawn, and the second element would store the list of the other lines.
I find it meaningful to update the animation's state and the drawing in separate functions.
I have a program which performs a long-going calculation where the result is shown as a plot.
I am currently using Chart-0.14 for this. I want to show the partial results, and update during calculations.
Graphics.Rendering.Chart.Gtk.updateCanvas :: Renderable a -> DrawingArea -> IO Bool seems to do that, but I do not find any way to get a DrawingArea from the plot. The function renderableToWindow :: Renderable a -> Int -> Int -> IO () does not return anything (and furthermore it does not return before the window is closed).
I would like to do something like the following:
main = do
drawingArea = forkRenderableToWindow (toRenderable $ plotLayout $
plot [0,0.1..10] sin "sin(x)") 640 480
updateCanvas (toRenderable $ plotLayout $ plot [0,0.1..10] sin "sin(x)") drawingArea
How should I do this? Would I need to reimplement the functions in Graphics.Rendering.Chart.Gtk with a version that returns the DrawingArea and in some way (how would I do this? forkIO?) returns immediately without closing the window?
You are looking for createRenderableWindow and then you need to use the GTK operations to work on the given Window - I don't think the Chart package exports any higher level operations on Windows.
EDIT2: So ignore the below - it doesn't work even with GUI initilization. My comment was a guess based on types.
EDIT:
Here is some example code. Understand, I'm just piecing things together based on the types. There might be better ways to do things if you ask someone who actually knows the library.
Below we use:
createRenderableWindow - this was the crux of my answer
castToDrawingArea - This is needed to get a DrawingArea from the Window type provided by GTK. These casts are taking place of C++ OO inheritance, I think.
widgetShowAll - because we haven't actually displayed the window, we best do that. I stole this function after looking at the source for renderableToWindow.
updateCanvas - I just saw this in the haddock documentation and figured it is why you wanted a DrawingArea in the first place.
Now for the code:
import Graphics.Rendering.Chart.Gtk
import Graphics.Rendering.Chart.Renderable
import Graphics.UI.Gtk.Misc.DrawingArea
import qualified Graphics.UI.Gtk as G
main = do
win <- createRenderableWindow emptyRenderable 400 400
let draw = castToDrawingArea win
G.widgetShowAll win
updateCanvas emptyRenderable draw