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.
Related
I'm making an Asteroids clone in Haskell using Gloss.
I want the spaceship to come back from the left if it goes out on the right side of the screen.
This is made unnecessarily tricky by the fact that the origin (0, 0) is in the middle of the screen.
Is there a way to move the origin to the top (or bottom) left?
My Main module (where I'm interacting with Gloss) looks like this:
module Main where
import Graphics.Gloss (Display (InWindow), black, play)
-- my own imported code
import Assets (loadAssets)
import GameState (defaultHeight, defaultWidth, initGameState)
import HandleInput (handleInput)
import ProgressGameState (progressGameState)
import Render (render)
windowDisplay :: Display
windowDisplay = InWindow "Window" (defaultWidth, defaultHeight) (10, 10)
main :: IO ()
main = do
assets <- loadAssets
play
windowDisplay
black
60
initGameState
(render assets)
handleInput
progressGameState
I don't think there's an easy way to move the origin. But the conversion between whatever your format is a simple translation. You can use a function (or even a smart constructor) to do the calculation between the two.
For example, if (0,0) is at the center of a picture with the dimensions (x,y), but you would want it to be at the top left, all you have to do is substract (x/2,y/2) from all your points to make the math right again.
Suppose I have a button and a square, like this:
main = mainWidget $ do
x <- button "Change color."
y <- toggle False x
z <- mapDyn style y
elDynAttr "div" z blank
style :: Bool -> Map String String
style b | b = "style" =: "height: 10ex; width: 10ex; background-color: #f00;"
| otherwise = "style" =: "height: 10ex; width: 10ex; background-color: #900;"
The colour of the square will alternate between bright and dark red when the button is pressed.
I want to replace the button with a green rectangle in such a way that, when mouse pointer is over it, the red square is bright, otherwise dark. For bonus points, I would like the green rectangle to be a div tabindex=0 and to make sure the red square is bright when it has focus, but, once I understand the general approach, I will likely be able to figure that out by myself.
My guess is that there is some API that allows adding event handlers to el, but I did not find one so far. And maybe there is a different approch altogether, that I could not know?
The stuff you need is all found in Reflex.Dom.Widget.Basic. If you create a widget with a function that has a prime in its name, such as el', you can get a hold of an object of type El, which is a member of HasDomEvent. domEvent, the only method of this class, lets you extract an event stream corresponding to one of a selection of event names. Mixing the events together and converting them to an appropriate Dynamic is then done with some usual Reflex tooling found in Reflex.Class and Reflex.Dynamic. You do need to familiarize yourself with these interfaces, but there is a helpful quick reference.
(Make sure to use the documentation matching the version of Reflex you build against, as there are significant changes between versions.)
One way to encode your case is as follows:
main = mainWidget $ do
y <- greenBox
z <- mapDyn style y
elDynAttr "div" z blank
style :: Bool -> Map String String
style ... -- Abridged for brevity.
greenBox :: MonadWidget t m => m (Dynamic t Bool)
greenBox = do
(e, _) <- elAttr' "div" ("style" =: "height: 10ex; width: 10ex; background-color: #0c0;") blank
let
eEnter = domEvent Mouseenter e
eLeave = domEvent Mouseleave e
holdDyn False . leftmost $ [True <$ eEnter, False <$ eLeave]
What we do here is create a widget for the green box that returns a Dynamic indicating whether it is "activated". Everything else is just as it were in your initial example.
Thanks to /u/dalaing for advising me on this.
I would like to create a text box that I can set to have a particular width and height. However, the text box that I create appears to not have a width and height in the first place. E.g. the following code:
main = do
putStrLn $ show $ width $ myText
putStrLn $ show $ height $ myText
mainWith myText
myText :: Diagram B
myText = text "here" # lw 1 # fontSizeL 0.2 # fc grey # scaleX 1
results in a blank diagram (with no text), and prints "0.0" as the width and height of the text box:
ghc --make Main.hs && ./Main -o circle.svg -w 400
[1 of 1] Compiling Main ( Main.hs, Main.o )
Linking Main ...
0.0
0.0
In order to get the text to appear it seem that I need to place it on top of something else. E.g. the following code:
main = do
putStrLn $ show $ width $ myDiagram
putStrLn $ show $ height $ myDiagram
mainWith myDiagram
myDiagram :: Diagram B
myDiagram = myText <> myBackground
myText :: Diagram B
myText = text "here" # lw 1 # fontSizeL 0.2 # fc grey # scaleX 1
myBackground :: Diagram B
myBackground = rect 1 1 # fc black
produces the grey text within a black background as expected.
Why does the text box not have a size?
How can I set the length and width of a text box to a certain value?
Why does the text box not have a size?
Because text handling is not only highly dependent on the backend used to render the diagrams (cf. the User's Guide) but also potentially dependent on how fonts are configured in the system used to run the program.
How can I set the length and width of a text box to a certain value?
Use text handling functions provided by your chosen backend, if they exist. For instance, diagrams-cairo provides functions that use Pango to render text on its own, though with the significant annoyance of them being IO functions, as they need to query font information from the system.
Use SVGFonts to stroke the text independently of the backend. There are a few inconveniences with this approach as well (limited choice of fonts unless you follow the instructions in the documentation to convert your chosen font to the SVGFonts format yourself; fonts with large sets of characters might lead to a small but noticeable delay when they are first used by your program, as they have to go through an initial processing), but at least you don't have to bother with IO or backend-specific quirks.
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
Could someone explain me what this code does line by line ?
how t ounderstand excactly first line with declaration ?
what does it mean: [Prop (Grid ())]?
thanks for help
gridCtrl :: Window a -> [Prop (Grid ())] -> IO (Grid ())
gridCtrl parent props
= feed2 props 0 $
initialWindow $ \id rect -> \props flags ->
do g <- gridCreate parent id rect flags
gridCreateGrid g 0 0 0
set g props
return g
In wxHaskell, controls have attached properties which can be read or changed. The stanza [Prop (Grid ())] can be understood as "a list of properties for any Grid type".
This is wxHaskell's way of dealing with the fact that the wxWidgets library, which it is built on, is object-oriented. Grid () actually means "anything in the inheritance hierarchy from which Grid derives" - i.e. Grid, ScrolledWindow, Panel, Window, EvtHandler, wxObject (you can follow this through if you start at at http://wxhaskell.sourceforge.net/doc/Graphics-UI-WXCore-WxcClassTypes.html#204)
When you look at the documentation of a Control (e.g. ListCtrl) you will find that it is reported as having a set of Attributes and Instances. Basically, you can use those which apply to the hierarchy for Grid. For example, Grid derives from ScrolledWindow, so you can use:
Attribute scrollRate
Attributes of Colored class e.g. bgcolor, color
etc.
You use these properties as follows, e.g.
g <- gridCtrl parent [color := red, bgcolor := green, scrollRate := 41]
...
set g [color := blue]
Line by line, the code reads something like the following:
Using the supplied properties (props)
and an initialWindow (which will fill in the window id and initial rect and flags), call the floowing wrapped functions in order:
gridCreate to create a new Grid instance
Using the new grid instance, set the grid inside with 0 rows, 0 columns and nothing selected.
Apply the properties (props) supplied by the caller to the grid (e.g. put data in, set styles etc.)
The part which makes the code hardest to read is the feed2 function, and that fact that the code is written in '$' style to provide the correct parameters to feed2. My slightly hand-waving explanation above should be enough, but if you want to understand the details, understand that feed2 is just composed inverted function application
feed2 x y f = f x y
then replace the '$' applications with parentheses. This doesn't look as cute, but is easier to read.
gridCtrl parent props =
feed2 props 0
(initialWindow (\id rect ->
\props flags ->
do
g <- gridCreate parent id rect flags
gridCreateGrid g 0 0 0
set g props
return g )))