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)
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
In my user interface (coded in Haskell) I want to set some widget background and foreground colours to values computed by the application (as opposed to a theme colour). I initially used widgetOverrideBackgroundColor and widgetOverrideColor for this, despite the fact that they are deprecated. However these have recently stopped working (fair enough, they are deprecated).
What is the easiest way to get the functionality of widgetOverrideColor and its relatives? Is there a way of programmatically generating a style provider for a single widget and setting the colours there (the widgets are also generated dynamically)? Or is the solution to intercept the draw callback? If so, how can I set the colours and then hand back control to the original?
I've now managed to do this using a combination of CSS and intercepted draw signals. The code is in Haskell as its what I'm writing in, but it should be translatable to other languages.
The basic technique is to add some extra Cairo code to the draw callback to paint a different background, and then use CSS to set make the widget itself transparent. This code uses the gi-gtk library for GTK3, the cairo library for drawing, and the colour library for colours. This has been extracted and slightly simplified from a larger program. I hope I haven't left anything dangling.
import qualified GI.Cairo.Structs.Context as Gtk
import qualified GI.Gtk as Gtk
import qualified Graphics.Rendering.Cairo as Cairo
import qualified Graphics.Rendering.Cairo.Internal as CI
import qualified Graphics.Rendering.Cairo.Types as Cairo (Cairo (Cairo))
import qualified Data.Colour as C
import qualified Data.Colour.CIE as C
import qualified Data.Colour.SRGB as C
customPaint :: (Gtk.isWidget w) => w -> Maybe Colour -> Gtk.Context -> IO ()
customPaint widget Nothing _ = do
-- No background, so reset everything.
style <- Gtk.widgetGetStyleContext widget
mapM_ (Gtk.styleContextRemoveClass style) [lightClass, darkClass]
customPaint widget (Just c) ctx = do
-- Get the dimensions of the background.
w <- Gtk.widgetGetAllocatedWidth widget
h <- Gtk.widgetGetAllocatedHeight widget
-- Set the widget style to transparent using a class.
style <- Gtk.widgetGetStyleContext widget
mapM_ (Gtk.styleContextRemoveClass style) [lightClass, darkClass]
Gtk.styleContextAddClass style $ if C.luminance c > 0.5 then lightClass else darkClass
-- Draw the background using the Cairo Render monad.
runRender ctx $ do
let
C.RGB r1 g1 b1 = C.toSRGB c
Cairo.setSourceRGB r1 g1 b1
Cairo.rectangle 0 0 (fromIntegral w) (fromIntegral h)
Cairo.fill
-- Conversion between gi-gtk Cairo Context and Cario library Render monad. Only
-- needed because they have different ways of wrapping the underlying C object.
runRender ctx action =
Gtk.withManagedPtr ctx $ \p ->
runReaderT (CI.runRender action) (Cairo.Cairo (castPtr p))
-- CSS class names. "light" uses black text on a pale background. "dark" is the opposite.
lightClass = "transparent-light"
darkClass = "transparent-dark"
Then you can store the colour you want in an IORef and create a callback for the widget drawing signal like this:
Gtk.onWidgetDraw myWidget $ \ctx -> do
c <- readIORef colourRef
customPaint myWidget c ctx
The CSS for the application contains the following:
/* Custom CSS classes for coloured widgets.
The background is transparent. The foreground is either black or white.
*/
.hades-transparent-dark {
color: white;
background-color: transparent; }
.hades-transparent-light {
color: black;
background-color: transparent; }
Luckily I only need to set the background colour, with the foreground colour being either black or white for contrast with the background. I don't know how I would go about setting an arbitrary foreground colour.
The GTK documentation recommends using CSS:
gtk_widget_override_background_color has been deprecated since version 3.16 and should not be used in newly-written code.
This function is not useful in the context of CSS-based rendering. If you wish to change the way a widget renders its background you should use a custom CSS style, through an application-specific GtkStyleProvider and a CSS style class. You can also override the default drawing of a widget through the “draw” signal, and use Cairo to draw a specific color, regardless of the CSS style.
For reference, in my own GTK3 application I used the following code to load custom CSS:
{-# LANGUAGE OverloadedStrings #-}
import GI.Gtk
main = do
-- stuff
prov <- cssProviderNew
cssProviderLoadFromPath prov "path/to/custom/css/file.css"
screenGetDefault >>= \case
Just screen -> styleContextAddProviderForScreen screen prov 800
Nothing -> return ()
For the CSS file itself, you can refer to widgets by their names. The following CSS is untested, but should work:
#widgetname1 {
color: red;
background-color: green;
}
I don’t know if there is a way of doing this purely programatically, without any external CSS files (i.e. specifying the CSS inline); if I find a method I will update this answer.
I want to have lego style splitscreen camera with seamless transitions.
Anyone has any experience creating something like this? I thought of creating one normal camera and then another camera for second player that by default wouldn't be visible. Then, when i would want to show it, i would draw a triangle to split the screen and set it texture as camera #2 view.
I found this Unity implementation but i couldn't implement it in Godot. I've managed to create second viewport with it's own camera, but for some reason the view of the second camera is not showing anything. I'm thinking that the problem is that the world of the second viewport is different than the main viewport.
Source code can be found here.
I just setup a toy project to test this out and it turned out to be simpler than expected.
Here is an overview of the process and then code examples will follow.
Add one main camera
And a secondary camera with tree: Control > Viewport > Camera
Draw the shape of the split screen with Control using the draw_* api
Add a shader to Control that takes a texture and draws it at SCREEN_UV
Get the viewport texture from Viewport
Pass the viewport texture to the Control shader every frame.
Animate the split by animating and redrawing the Control shape.
I'm not sure how to do the border.
To make the split join you'll probably have to shift the Control shape by the thickness of border and then shrink that border as the cameras go towards each other. Use that distance between the players to calculate the border width.
The split border is also at an angle between the two players so when animating the shape you'll want to use that angle. This will make the joining of the viewport look smoother.
Control code:
extends Control
func _draw():
# in this case animate tl and bml to get the
# rotating split like effect in the lego game
var tl = Vector2()
var tr = rect_size
tr.y = 0
var br = rect_size
var bml = rect_size
bml.x /= 2.0
draw_polygon([tl, tr, br, bml, tl], [Color(), Color(), Color(), Color(), Color()], [])
func _process(d):
material.set_shader_param('viewport', $Viewport.get_texture())
Shader Code:
shader_type canvas_item;
uniform sampler2D viewport;
void fragment(){
COLOR=texture(viewport, SCREEN_UV);
}
I hope this helps get you started!
It is a complex effect with many parts so be warned.
I found that the easiest way is to create a separate scene with your viewports and cameras which will be your main scene and then add your game scene under it like this:
Spatial
Viewport1
Camera1
Viewport2
Camera2
GameScene
You should then be able to make a ColorRect with a shader material and send in the textures from each viewport:
shader_type canvas_item;
render_mode unshaded, cull_disabled;
uniform sampler2D viewport1;
uniform sampler2D viewport2;
void fragment() {
vec3 view1 = texture(viewport1, UV).rgb;
vec3 view2 = texture(viewport2, UV).rgb;
vec3 col = vec3(0);
// mix them in a satisfying way depending on distance and angle between cameras
// float mixVal = <your formula here>
// col = mix(view1, view2, mixVal)
COLOR = vec4(col, 0.0); // this may not work in Godot shaders
}
This is a great guide to get you started:
https://docs.godotengine.org/en/3.1/tutorials/viewports/using_viewport_as_texture.html
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 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.