I am trying to modify the Elm example that shows a single spacer so that it renders multiple spacers of different colors:
import Color exposing (red, blue, yellow)
import Graphics.Element exposing (Element, color, spacer, flow, right)
colors = [ yellow, red, blue ]
presentColors : List Element
presentColors = List.map (color ??? (spacer 30 30)) colors
main : Element
main =
flow right presentColors
However as you can see the function color takes the color argument first and so I cannot create a partially applied version of it for List.map to use.
So how can I flip the arguments to color so that it can be partially applied?
As of Elm 0.19, flip is no longer included by default. The docs recommend named helper functions instead.
Go to the Elm (pre v0.19) libraries page. Press Standard Libraries. In the search box, type in flip and click the function that comes up. That'll give you the documentation for
flip : (a -> b -> c) -> b -> a -> c
Flip the order of the first two arguments to a function.
With which you can do
flip color (spacer 30 30)
which is the same thing as
\c -> color c (spacer 30 30)
Flip was removed from elm/core in 0.19. You could try:
pilatch/flip package instead.
Related
How is it possible to render a rectangle with the background color of selections in GTK+3. I cannot find any API to do that:
static gboolean draw_callback (GtkWidget *widget, cairo_t *cr, gpointer data)
{
auto state=reinterpret_cast<State*>(data);
auto width = gtk_widget_get_allocated_width (widget);
auto height = gtk_widget_get_allocated_height (widget);
auto context = gtk_widget_get_style_context (widget);
gtk_render_background(context,cr,0,0,width,height);
cairo_rectangle(cr,0,height*(1.0 - state->max),width,height*(state->max - state->min));
cairo_set_source_rgb(cr, 0.05,0.6,0.15); //What color should be used here?
cairo_fill (cr);
cairo_set_source_rgb(cr,0.01,0.3,0.07); //And here
auto mid=height*(1.0 - 0.5*(state->min + state->max));
cairo_move_to(cr,0, mid);
cairo_line_to(cr,width,mid);
cairo_stroke(cr);
return FALSE;
}
Use gtk_render_frame() and gtk_render_background(), and set up the GtkStyleContext you obtain from the GtkWidget instance with the CSS state you want to replicate.
If you want to adhere to the theme, then you cannot draw yourself; and CSS does not have "colors": each CSS state can have multiple layers that include images, gradients, and complex blend modes.
Well, here is my hack:
ColorRGBA get_ambient_color(GtkWidget* widget)
{
auto surface=cairo_image_surface_create(CAIRO_FORMAT_ARGB32,4,4);
auto cr=cairo_create(surface);
while(widge!=NULL)
{
auto context=gtk_widget_get_style_context(widget));
gtk_render_background(context,cr,0,0,1,1);
cairo_surface_flush(surface);
auto content=cairo_image_surface_get_data(surface);
if(content[3]==255)
{
auto ret=ColorRGBA{content[2]/255.0f,content[1]/255.0f,content[0]/255.0f,content[3]/255.0f};
cairo_destroy(cr);
cairo_surface_destroy(surface);
return ret;
}
// Surface is not opaque yet. Continue to parent container.
widget_handle=gtk_widget_get_parent(GTK_WIDGET(widget_handle));
}
cairo_destroy(cr);
cairo_surface_destroy(surface);
return ColorRGBA{1.0f,1.0f,1.0f,1.0f};
}
It seams that I have failed to convince people, why you need the ambient colour, so here are two use-cases:
Determine if we are using a dark/light theme. For some applications, this is sufficient. Querying the state only works if the theme supports dark/light modes. This proves the actual result.
Use as input colour for simulating global illumination. The shading of widgets should be affected by the ambient, hence the name. Another good name would be get_average_background. Themers: please don't use gradients with high contrast.
Case 1: A plot
Now you say that the colour of cursors and function graphs should be themable. That is simply not possible: The user of this plot widget can add as many curves and cursors as he wishes, and the easiest way to differentiate them is to use distinct colours.
What about curve and cursor lightness? If the background is dark, then the curve should be light and vice versa. And what background should be chosen? Ideally, something close the the background of the parent widget, but if the theme is regular, white for light, and black for dark would work. Do you notice that the curves are darker in the second figure?
Case 2: A checkbox that looks like a metallic toggle switch button
With the following technique, I have created a switch that looks exactly as if it were rendered through the Cycles path tracer. This is implemented in Gtk+2, but the algorithm is the same.
The two input images
The code
GtkAllocation alloc;
gtk_widget_get_allocation(widget,&alloc);
auto width=alloc.width;
auto context=CairoContext( gdk_cairo_create(gtk_widget_get_window(widget)) );
auto w_in=cairo_image_surface_get_width(light);
auto h_in=cairo_image_surface_get_height(light);
// Render direct lighting
auto surf_temp=CairoSurface( cairo_image_surface_create(CAIRO_FORMAT_ARGB32,w_in,h_in) );
auto context_temp=CairoContext( cairo_create(surf_temp) );
cairo_set_source_surface(context_temp,light,0,0);
cairo_set_operator(context_temp,CAIRO_OPERATOR_OVER);
cairo_paint(context_temp);
//Render ambient reflections
auto surf_temp_2=CairoSurface( cairo_image_surface_create(CAIRO_FORMAT_ARGB32,w_in,h_in) );
auto context_temp_2=CairoContext( cairo_create(surf_temp_2) );
cairo_set_source_surface(context_temp_2,background,0,0);
cairo_set_operator(context_temp_2,CAIRO_OPERATOR_OVER);
cairo_paint(context_temp_2);
cairo_set_operator(context_temp_2,CAIRO_OPERATOR_MULTIPLY);
//Multiply reflections with the background color
cairo_set_source_rgb(context_temp_2, color_bg.r, color_bg.g, color_bg.b);
cairo_rectangle(context_temp_2, 0, 0, w_in, h_in);
cairo_mask_surface(context_temp_2,surf_temp,0,0);
//Add the results
cairo_set_source_surface(context_temp,surf_temp_2,0,0);
cairo_set_operator(context_temp,CAIRO_OPERATOR_ADD);
cairo_mask_surface(context_temp,surf_temp,0,0);
//Scale and move things into place
auto s=static_cast<double>(width)/static_cast<double>(w_in);
cairo_translate(context,alloc.x,alloc.y);
cairo_scale(context,s,s);
cairo_set_source_surface(context,surf_temp,0,0);
cairo_set_operator(context,CAIRO_OPERATOR_OVER);
cairo_paint(context);
Thoughts
The first example boils down to a light/dark query which is currently missing. Maybe querying colours is not required for this to work, but then there has to be an API controlling the shape and blending mode when rendering the background. For example, to render the ambient reflection, I use multiply rather than over. Also, gtk_render_background appears to be a no-op, since GtkDrawingArea has zero opacity (that's why I needed the loop). To be useful, it must use the background as it appears on screen, not the background of the current widget.
I try to merge two signals. One is Mouse.clicks and another is Keyboard.space.
On clicks, I should get a Signal(Int,Int) from Mouse.position as return value
On space, I should get something different so I can identify different signal is triggered.
My idea is:
type Event = Click | Space
mergedSignal : Signal Event
mergedSignal =
let
clickSignal = map (\event -> Click) Mouse.clicks
timeoutSignal = map (\event -> Space) Keyboard.space
in
merge clickSignal timeoutSignal
and get position somehow:
positionOnClickSignal:Signal (Int,Int)
positionOnClickSignal = sampleOn Mouse.clicks Mouse.position
Obviously, it is wrong.
It sounds like you want the mouse position to carry over as part of the event. In that case, you could redefine Event as
type Event
= Click (Int, Int)
| Space
Inside your mergedSignal, the clickSignal is currently just checking for Mouse.clicks but based on your description and other example, I think you actually want that to be based off positionOnclickSignal, which gives you a Signal (Int, Int), and using that, you can now populate the (Int, Int) portion of the Click (Int, Int) event like this:
clickSignal =
map Click positionOnClickSignal
You'll notice that I took out the parenthesis in the above. That is more idiomatic for Elm, because Click is in essence a function that takes one (Int, Int) parameter, which will be passed in from the map function. It could have easily been written like this:
clickSignal =
map (\pos -> Click pos) positionOnClickSignal
Now, if you're just trying to see some debug text of this on screen, a quick and easy way to go about that is to use show from the Graphics.Element package.
import Graphics.Element exposing (show)
main =
map (show << toString) mergedSignal
That will give you some debug text shown as the only thing on the page, and you could easily toss it up on http://elm-lang.org/try for testing.
How can I convert Mouse.position into Form, so I can display it in a collage? The following code displays <Signal> instead of the actual mouse coordinates:
render (x, y) =
let mousePos = toForm (show Mouse.position)
in collage 400 400 [mousePos]
It's curious that in this example http://elm-lang.org/examples/mouse-position, the show function actually transforms Mouse.position into a string with coordinates, but that is because the show function is used to filter a Signal(Int, Int) into a tuple of Signal values.
So my question is, how do I convert a Signal(Int, Int) into a Form, so that it shows the tuple values?
You are looking for Graphics.Collage.toForm which has the type Element -> Form.
It also sounds like you don't quite understand what Signal.map is doing. It takes a function to be applied to each value of a Signal. I've tried to use it in several contexts in the following example.
import Graphics.Element exposing (..)
import Graphics.Collage
import Graphics.Collage exposing (Form)
import Mouse
--This is the function you are trying to construct.
--It takes in a position, converts it to an element,
--using show and then converts it to a Form.
formPosition : (Int, Int) -> Form
formPosition pos =
let element = show pos -- element : Element
in Graphics.Collage.toForm element
-- We now want to apply our formPosition function to the
-- Signal containing all mouse position changes.
-- So we use Signal.map to apply formPosition to all values
-- of Mouse.position
formSignal : Signal Form
formSignal = Signal.map formPosition Mouse.position
-- Eventually we want to render this on the screen and the
-- function to do this requires a List Form not just a single
-- Form. So we write a function which returns a Singleton list
-- and apply it to each value in our formSignal.
formListSignal : Signal (List Form)
formListSignal = Signal.map (\n -> [n]) formSignal
-- Finally, we must turn that into a Signal Element to render
-- on the screen. We partially apply Graphics.Collage.collage
-- to return an element of size 400x400 and apply it to the
-- values of formListSignal by using Signal.map again
elementSignal : Signal Element
elementSignal = Signal.map (Graphics.Collage.collage 400 400) formListSignal
-- Finally we hand this off to main and it renders
main : Signal Element
main = elementSignal
A simpler version would likely combine all of the conversions into a single function. I just wanted to emphasize how Signal.map worked. I hope this helps!
I am writing a roguelike in Elm, where there is a discrete 50x50 grid (see share-elm.com snippet). A roguelike is a video game, where objects (like enemies, items, walls, etc) are represented by ASCII characters. Therefore I should be able to have hundreds of different ASCII characters, aligned in a rectangular grid. Every character should be strictly within its grid cell.
To create this grid, I put every character in a square container (1/50 size of the actual game container). This means I can have 2500 containers in the game maximum. Elm creates <div> elements for containers, even if I convert these containers to Form and put them inside a collage. This makes my Firefox 39.0 very slow in performance.
How do I create a rectangular grid with nicely aligned ASCII characters (and possibly some other graphical elements) within its grid cells, so that no matter how many elements I have at the same time, the collage still stays quick and responsive? And what is the general idiomatic approach every time I'm writing a program with lots of containers and other elements inside a collage? Or maybe there is a completely different approach to creating snappy rectangular grids in Elm?
One possibility (if you don't mind writing some HTML instead of using collage/container) would be to use the Html.Lazy module. You could, for example, wrap the rendering of each "row" of the display in a lazy and it would only re-render the rows that changed (which should only be 1-2 per timestep/movement).
What you're looking for here is Graphics.Collage.text. When you turn an Element into a Form Elm will take the general approach that can place any Element like a Form, but it doesn't actually draw it on the canvas. (Yay, implementation details). If you instead go straight from Text to Form, it's statically known that it's text, so the faster method of drawing text on a canvas can be used. This is a simple change:
view : (Int, Int) -> Element
view (w,h) =
let
s = min w h -- collageSize
forms = List.map (\(x,y) -> move (s,s) (x,y) playerForm)
<| cartesian 0 (screenSize-1) 0 (screenSize-1)
playerForm = "#"
|> Text.fromString
|> Text.height ((toFloat s) / screenSize)
|> C.text
-- |> E.centered
-- |> E.container (s//screenSize) (s//screenSize) E.middle
-- |> C.toForm
in
E.color Color.lightGray
<| E.container w h E.middle
<| E.color Color.white
<| C.collage s s forms
Instead of the three lines in comments, it's just the C.text. You can see the responsiveness in the updated share-elm snippet.
Note that you can no longer select the text! But otherwise it should be much better.
I need to add a batch of arrow annotations to an image, I know all the start and end points of the arrows.
And I've put them into an image (2 columns, many rows) which I used as a data sheet, how to realize it in script?
I noticed that in the DM help manual that the line annotation has the attributes-- start point and end point.
But the function to create an arrow annotation jsut looks like this:
Component NewArrowAnnotation( Number top, Number left, Number bottom, Number right )
Does that mean the number top and left define the start point, number bottom and right the end point?
I also need to change the color of the annotations, and add some text next to them (either side is OK, but please show me how to control it).
What isn't always clear from the current documentation is that annotations belong to the component object. You therefore find all required commands documented in the component section of the help documentation.
Note that also imageDisplays are themselves a subclass of the component object, so you can add "annotations" (components) to "imageDisplay" (components) using the ComponentAddChild... commands.
A script to add a simple arrow-annotation (pointing at 200/200) can therefore look like the following:
image test := RealImage( "Test", 4, 512, 512 )
test.ShowImage()
ImageDisplay disp = test.ImageGetImageDisplay(0)
component arrow = NewArrowAnnotation(100,100,200,200)
arrow.ComponentSetForegroundColor( 0, 1, 1 )
disp.ComponentAddChildAtEnd( arrow )