123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231(* A Box.t is a passive widget that contains a rectangular texture, which can be
specified by a color or a Style.background --- which means it can contain an
Image.t. *)(* The rectangle can have rounded corners, and a border. *)(* A Box.t can be used directly as a background for layouts that support
Layout.background *)(* These various background types are a bit confusing. Maybe one should unify
them. *)openB_utilsopenTsdlmoduleTheme=B_thememoduleVar=B_varmoduleDraw=B_drawmoduleImage=B_imagemoduleStyle=B_style(* "themes/textures/subtle-patterns/subtle-pattern-7.bmp" *)(* "themes/textures/grey_wash_wall/grey_wash_wall.bmp" *)typet={render:(Draw.textureoption)Var.t;mutablestyle:Style.t;(* border is drawn *inside* the box *)mutablesize:int*int;(* size incl. border if line width > 0 *)(* note that this size is not really used. g.w, g.h is used instead when
displaying, which is good if this box is a background of a room, and we
changed the size of the room... *)}(* TODO report correct size if line width < 0 *)letsizeb=b.sizeletdefault_size=(256,64)letdefault_background=Draw.box_bg_color(* Style.Solid Draw.(opaque pale_grey) *)letdefault_border=Style.(mk_border{color=Draw.(opaquegrey);width=1;style=Solid})(* not used *)letcreate?width?height?style()=letstyle=default_fnstyle(Style.create)inletw,h=default_sizein{render=Var.createNone;style;size=(defaultwidthw),(defaultheighth)}letunload_textureb=matchVar.getb.renderwith|None->()|Sometex->beginDraw.forget_texturetex;Var.setb.renderNoneendletunloadb=unload_textureb;Style.unloadb.styleletget_styleb=b.styleletset_stylebstyle=unloadb;b.style<-styleletset_backgroundbbkg=set_styleb(Style.with_bgbkgb.style)letresizesizeb=unloadb;b.size<-size(* TODO *)letfree=unload(************* display ***********)(* As all widget display functions, the geometry g must be already scaled. *)letdisplaycanvaslayerbg=letopenDrawin(* TODO: make sure hoffset <= h *)lettex=matchVar.getb.renderwith|Somet->t|None->lettarget=create_targetcanvas.rendererg.wg.hinletsave_target=push_targetcanvas.renderertargetin(* draw background *)begindo_option(Style.get_bgb.style)@@function|Style.Imageimg->printddebug_graphics"Create pattern background";letpattern=matchVar.getimg.Image.renderwith|Sometex->tex|None->beginignore(Image.displaycanvaslayerimg(make_geom~w:img.Image.width~h:img.Image.height()));matchVar.getimg.Image.renderwith|Sometex->tex|None->failwith"Image should have been rendered before"endinfill_patterncanvas.renderer(Sometarget)pattern|Style.Solidcolor->set_colorcanvas.renderercolor;go(Sdl.render_clearcanvas.renderer);(* B_border.essai canvas.renderer; *)(* essai corner_gradient2 *)(* corner_gradient2 canvas.renderer (opaque black) (set_alpha 0 black);
*)|Style.Gradient{Style.colors;angle}->gradientv3canvas.renderer~anglecolors;(* ESSAI circle *)(* print_endline "CIRCLE";
* let c = transp black in
* (\*disc canvas.renderer c (g.w/2) (g.h/2) (g.h/2-5);*\)
* (\*annulus_octants canvas.renderer ~octants:(1+2) c (g.w/2) (g.h/2) 20 (g.h/2-5);*\)
* rounded_box canvas.renderer c
* ~w:(g.w/2) ~h:(g.h/2) ~thick:10 ~radius:25 (g.w/2) (g.h/2);
* let c = opaque cyan in
* circle canvas.renderer ~width:4 c (g.w/4) (g.h/4) (g.h/2); *)(* FIN ESSAI *)end;pop_targetcanvas.renderersave_target;(* need to clip in case of rounded corners *)(* TODO unite with do_option b.border *)lettex=matchStyle.get_borderb.stylewith|Some({Style.radius=Someradius;_}asb)whenradius>0->letthick=imax0((Theme.scale_intb.Style.down.Style.width)-1)in(* avec ou sans le "-1" sont acceptables. "avec" crée un petit liseré
entre les deux couleurs transparentes. "sans" laisse un peu trop de
"transparent" aux coins. Si on évite les bordures transparentes (ce
qui est à conseiller), "avec" est mieux. *)(* we have a choice here. If both the border and the background have
alpha components, do we draw the border on top of the background
(blending the 2 alphas) (thick=0 or 1), or do we draw them
non-intersecting (thick = width or width -1, very difficult to be
exact), so that on a white page, they both appear the way the user
probably wanted to...? In inkscape they have chosen half-way: the
background extends to _half_ the width of the border (thick =
width/2)... In our case it's even more difficult because we may
have an image instead of a plain background color, so we have to
clip it rounded... (with "mask_texture" below) *)letradius=max0(Theme.scale_intradius-thick)in(* TODO treat case line width < 0 *)letshape=create_targetcanvas.rendererg.wg.hinletbg=set_alpha0blackin(* any fully transparent color will do as long as we don't blend onto
the resulting texture. *)letsave_target=push_target~clear:true~bgcanvas.renderershapeingo(Sdl.set_render_draw_blend_modecanvas.rendererSdl.Blend.mode_none);filled_rounded_box~antialias:truecanvas.renderer(opaqueblack)(* for [mask_texture], any opaque color will do, but for
[fast_mask_texture] we need black. *)~w:(g.w-2*thick)~h:(g.h-2*thick)~radius(thick)(thick);(* TODO check if this works when Solid background has alpha channel
and thick = 0 ... *)pop_targetcanvas.renderersave_target;lett=fast_mask_texture~mask:shapecanvas.renderertargetinforget_texturetarget;forget_textureshape;t|_->targetin(* draw border *)(* => TODO use Draw.rectangle (but for now only works if line width is
constant) . For the moment we use the style of the bottom
border. *)(* TODO The texture tex has been alpha-masked but the color still remains
hidden... thus if we blend the border onto the texture, because of the
blending formula, the hidden color might show up again, see example
1h. But setting mode_none is not good either because there will be some
white in the inner side of the border... The best way would be to ask
"rounded" to use "blend" inside and "none" outside... TODO *)(* TODO? use the new https://wiki.libsdl.org/SDL_ComposeCustomBlendMode*)do_option(Style.get_borderb.style)(funbrd->letsave_target=push_target~clear:falsecanvas.renderertexingo(Sdl.set_render_draw_blend_modecanvas.rendererSdl.Blend.mode_blend);letopenStyleinbeginmatchbrd.radiuswith|None->beginboxcanvas.renderer~bg:brd.up.color00g.w(Theme.scale_intbrd.up.width);letdw=Theme.scale_intbrd.down.widthinboxcanvas.renderer~bg:brd.down.color0(g.h-dw)g.wdw;boxcanvas.renderer~bg:brd.left.color00(Theme.scale_intbrd.up.width)g.h;letrw=Theme.scale_intbrd.right.widthinboxcanvas.renderer~bg:brd.right.color(g.w-rw)0rwg.h;end|Someradius->letradius=Theme.scale_intradiusinletthick=Theme.scale_intbrd.down.widthinrounded_boxcanvas.rendererbrd.down.color~w:g.w~h:g.h~radius~thick00end;pop_targetcanvas.renderersave_target);Var.setb.render(Sometex);texin(* Essai shadow. TODO save the textures and use them as long as sizes don't
change *)letdst=geom_to_rectginletshadow_blits=matchStyle.get_shadowb.stylewith|None->[]|Somes->ifdefaults.Style.radius0>s.Style.widththen(printd(debug_graphics+debug_warning)"Shadow with rounded corner not implemented yet.";[](* TODO *))else(box_shadow~voffset:g.voffsetcanvaslayer~color:black~radius:(Theme.scale_ints.Style.width)~size:(Theme.scale_ints.Style.size)~offset:(Draw.scale_poss.Style.offset)dst)inList.rev((make_blit~voffset:g.voffset~dstcanvaslayertex)::shadow_blits)