fjvallarino / monomer

An easy to use, cross platform, GUI library for writing Haskell applications.
BSD 3-Clause "New" or "Revised" License
575 stars 42 forks source link

[Question] How to render changeable picture efficiently enough? #283

Open Lev135 opened 1 year ago

Lev135 commented 1 year ago

I want to show square grid with about 100x100 colored cells. The state of the grid (i. e. the colors of the cells) shouldn't change frequently. However, the simplest solution, which I've tried: to create a widget and draw the grid in render function works very poorly, since rerendering is called too often. Is there any way to make it better?

```hs makeGrid :: () => (Int, Int) -> Widget s e makeGrid (w, h) = createSingle () def { singleGetSizeReq = getSizeReq , singleRender = render } where cellSize = 10 coord n = fromIntegral n * cellSize getSizeReq _wenv _node = (fixedSize $ coord w, fixedSize $ coord h) render _wenv node renderer = do let vp = node ^. L.info . L.viewport origin = Point (vp ^. L.x) (vp ^. L.y) drawInTranslation renderer origin do for_ [0 .. w - 1] \i -> do for_ [0 .. h - 1] \j -> do let rect = Rect (coord i) (coord j) (coord 1) (coord 1) s = Just $ BorderSide 0.1 gray bd = Border s s s s drawRectBorder renderer rect bd Nothing ```
Deltaspace0 commented 1 year ago

@Lev135 Hi! In color picker widget there is a pattern with alternating colors to indicate transparency which is implemented using imageMem widget: https://github.com/fjvallarino/monomer/blob/67f747a61f996389828a5845da923bdcb9cb3739/src/Monomer/Widgets/Singles/ColorPicker.hs#L265-L276 Perhaps you can use imageMem to render your square grid.

fjvallarino commented 1 year ago

Hi @Lev135!

The main problem is you're drawing quite a few items using a helper function that is not particularly efficient. This function is used to render widget borders in several scenarios, and because of that it's too general; I added a note for myself to review it for the most basic case.

Based on what I see from the code, is the objective only drawing a grid, or are you planning on drawing squares in the viewport?

If you are only going to draw the borders, the simplest/most efficient solution is just drawing ten lines horizontally and ten lines vertically.

If you need to draw individual blocks and want them to have their own borders (be it because there are spaces between blocks or the border colors are different), you could try using Renderer's renderRect, which is a lower level version of drawRect and drawBorderRect. In this case you'll have to call the begin/end functions manually:

beginPath renderer
setFillColor renderer rectColor
renderRect renderer rectBounds
fill renderer

Alternatively, if you only want to draw borders, you can call:

setStrokeColor renderer color
setStrokeWidth renderer width
Lev135 commented 1 year ago

In my case I have colored cells, so solution with drawing lines doesn't work. ReplacingdrawRect with renderRect doesn't seems to have a notable effect. For @Deltaspace0 suggestion to use imageMem: I can't see any drawing primitives for such images... Maybe I've missed something. To be honest, I haven't quite figured out how it works and how it should solve my problem: render function will use a ready picture? For ColorPicker there should be no efficiency difficulies at all (untill we haven't several thousands of them).