halfway to graphv_webgl replacing wall
This commit is contained in:
474
human.ml
474
human.ml
@ -22,221 +22,26 @@ some options:
|
||||
|
||||
open Lwt.Infix
|
||||
module F = Fmt
|
||||
module Istore = Irmin_unix.Git.FS.KV (Irmin.Contents.String)
|
||||
|
||||
module Key = struct
|
||||
type special =
|
||||
[ `Enter
|
||||
| `Escape
|
||||
| `Tab
|
||||
| `Arrow of [`Up | `Down | `Left | `Right]
|
||||
| `Function of int
|
||||
| `Page of [`Up | `Down]
|
||||
| `Home
|
||||
| `End
|
||||
| `Insert
|
||||
| `Delete
|
||||
| `Backspace ]
|
||||
(* module Istore = Irmin_unix.Git.FS.KV (Irmin.Contents.String)*)
|
||||
|
||||
(** Type of key code. *)
|
||||
type code =
|
||||
[`Uchar of Uchar.t (** A unicode character. *) | special]
|
||||
module Istore =
|
||||
Irmin_git.Generic
|
||||
(Irmin_indexeddb.Content_store)
|
||||
(Irmin_indexeddb.Branch_store)
|
||||
(Irmin.Contents.String)
|
||||
(Irmin.Path.String_list)
|
||||
(Irmin.Branch.String)
|
||||
|
||||
type keystate =
|
||||
{ctrl: bool; meta: bool; shift: bool; super: bool; code: code}
|
||||
|
||||
module KeyS = struct
|
||||
type t = keystate
|
||||
|
||||
let compare = compare
|
||||
end
|
||||
|
||||
module Bind = struct
|
||||
(* parts stolen from lambda-term/src/lTerm_{edit,key}.ml{,i} *)
|
||||
module S = Zed_input.Make (KeyS)
|
||||
|
||||
type 'a t = 'a list S.t
|
||||
type 'a resolver = 'a list S.resolver
|
||||
type 'a result = 'a list S.result
|
||||
|
||||
type 'a state =
|
||||
{ mutable bindings: 'a t
|
||||
; mutable state: 'a result
|
||||
; mutable last_keyseq: keystate list
|
||||
; mutable last_actions: 'a list }
|
||||
|
||||
type mods = Ctrl | Meta | Super | Shift
|
||||
type key = C of char | U of code
|
||||
|
||||
let keystate_of_mods ks m =
|
||||
List.fold_left
|
||||
(fun ks m ->
|
||||
match m with
|
||||
| Meta -> {ks with meta= true}
|
||||
| Ctrl -> {ks with ctrl= true}
|
||||
| Super -> {ks with super= true}
|
||||
| Shift -> {ks with shift= true} )
|
||||
ks m
|
||||
|
||||
let add events action bindings =
|
||||
let events =
|
||||
List.map
|
||||
(fun (m, k) ->
|
||||
keystate_of_mods
|
||||
{ meta= false
|
||||
; ctrl= false
|
||||
; super= false
|
||||
; shift= false
|
||||
; code=
|
||||
( match k with
|
||||
| C c -> `Uchar (Uchar.of_char c)
|
||||
| U c -> c ) }
|
||||
m )
|
||||
events in
|
||||
S.add events action bindings
|
||||
|
||||
let default_resolver b = S.resolver [S.pack (fun x -> x) b]
|
||||
|
||||
let get_resolver result default =
|
||||
match result with S.Continue r -> r | _ -> default
|
||||
|
||||
let init bindings =
|
||||
{bindings; state= S.Rejected; last_keyseq= []; last_actions= []}
|
||||
|
||||
let resolve = S.resolve
|
||||
let empty = S.empty
|
||||
|
||||
type action =
|
||||
| Custom of (unit -> unit)
|
||||
| CustomLwt of (unit -> unit Lwt.t)
|
||||
| Zed of Zed_edit.action
|
||||
|
||||
let resolve_events (state : 'a state) events =
|
||||
List.flatten
|
||||
(List.filter_map
|
||||
(fun e ->
|
||||
match e with
|
||||
| `Key (`Press, (k : keystate)) -> (
|
||||
( match state.state with
|
||||
| Continue _ -> ()
|
||||
| _ -> state.last_keyseq <- [] ) ;
|
||||
state.state <-
|
||||
resolve k
|
||||
(get_resolver state.state
|
||||
(default_resolver state.bindings) ) ;
|
||||
state.last_keyseq <- k :: state.last_keyseq ;
|
||||
match state.state with
|
||||
| Accepted a ->
|
||||
state.last_actions <- a ;
|
||||
Some a
|
||||
| Rejected ->
|
||||
state.last_actions <- [] ;
|
||||
None
|
||||
| _ -> None )
|
||||
| _ -> None )
|
||||
events )
|
||||
|
||||
let actions_of_events (state : action state) events =
|
||||
List.flatten
|
||||
(List.filter_map
|
||||
(fun e ->
|
||||
match e with
|
||||
| `Key (`Press, (k : keystate)) -> (
|
||||
( match state.state with
|
||||
| Continue _ -> ()
|
||||
| _ -> state.last_keyseq <- [] ) ;
|
||||
state.state <-
|
||||
resolve k
|
||||
(get_resolver state.state
|
||||
(default_resolver state.bindings) ) ;
|
||||
state.last_keyseq <- k :: state.last_keyseq ;
|
||||
match state.state with
|
||||
| Accepted a ->
|
||||
state.last_actions <- a ;
|
||||
Some a
|
||||
| Rejected ->
|
||||
state.last_actions <- [] ;
|
||||
None
|
||||
| _ -> None )
|
||||
| _ -> None )
|
||||
events )
|
||||
|
||||
let process bindstate events =
|
||||
Lwt_list.iter_s
|
||||
(function
|
||||
| Custom f -> Lwt.return (f ())
|
||||
| CustomLwt f -> f ()
|
||||
| Zed _ -> Lwt.return_unit )
|
||||
(actions_of_events bindstate events)
|
||||
end
|
||||
|
||||
(* stolen from lambda-term/src/lTerm_{edit,key}.ml{,i} *)
|
||||
let string_of_code = function
|
||||
| `Uchar ch ->
|
||||
if Uchar.is_char ch then F.str "Char '%c'" (Uchar.to_char ch)
|
||||
else F.str "Char 0x%02x" (Uchar.to_int ch)
|
||||
| `Enter -> "Enter"
|
||||
| `Escape -> "Escape"
|
||||
| `Tab -> "Tab"
|
||||
| `Arrow `Up -> "Up"
|
||||
| `Arrow `Down -> "Down"
|
||||
| `Arrow `Left -> "Left"
|
||||
| `Arrow `Right -> "Right"
|
||||
| `Function i -> F.str "F%d" i
|
||||
| `Page `Up -> "Page Up"
|
||||
| `Page `Down -> "Page Down"
|
||||
| `Home -> "Home"
|
||||
| `End -> "End"
|
||||
| `Insert -> "Insert"
|
||||
| `Delete -> "Delete"
|
||||
| `Backspace -> "Backspace"
|
||||
|
||||
let to_string key =
|
||||
Printf.sprintf
|
||||
"{ control = %B; meta = %B; shift = %B; super = %B; code = %s }"
|
||||
key.ctrl key.meta key.shift key.super
|
||||
(string_of_code key.code)
|
||||
|
||||
let to_string_compact key =
|
||||
let buffer = Buffer.create 32 in
|
||||
if key.ctrl then Buffer.add_string buffer "Ctrl-" ;
|
||||
if key.meta then Buffer.add_string buffer "Meta-" ;
|
||||
if key.shift then Buffer.add_string buffer "Shift-" ;
|
||||
if key.super then Buffer.add_string buffer "Super-" ;
|
||||
( match key.code with
|
||||
| `Uchar ch ->
|
||||
let code = Uchar.to_int ch in
|
||||
if Uchar.is_char ch then
|
||||
match Uchar.to_char ch with
|
||||
| ( 'a' .. 'z'
|
||||
| 'A' .. 'Z'
|
||||
| '0' .. '9'
|
||||
| '_' | '(' | ')' | '[' | ']' | '{' | '}' | '#' | '~'
|
||||
| '&' | '$' | '*' | '%' | '!' | '?' | ',' | ';' | ':'
|
||||
| '/' | '\\' | '.' | '@' | '=' | '+' | '-' ) as ch ->
|
||||
Buffer.add_char buffer ch
|
||||
| ' ' -> Buffer.add_string buffer "space"
|
||||
| _ -> Printf.bprintf buffer "U+%02x" code
|
||||
else if code <= 0xffff then
|
||||
Printf.bprintf buffer "U+%04x" code
|
||||
else Printf.bprintf buffer "U+%06x" code
|
||||
| `Page `Down -> Buffer.add_string buffer "pgup"
|
||||
| `Page `Up -> Buffer.add_string buffer "pgdn"
|
||||
| code ->
|
||||
Buffer.add_string buffer
|
||||
(String.lowercase_ascii (string_of_code code)) ) ;
|
||||
Buffer.contents buffer
|
||||
end
|
||||
open Backend_js
|
||||
|
||||
module Event = struct
|
||||
open Tsdl
|
||||
open Key
|
||||
open Gg
|
||||
|
||||
type mouse = V2.t
|
||||
|
||||
type t =
|
||||
[ `Key of [`Press | `Release | `Repeat] * keystate
|
||||
[ `Key of [`Press | `Release | `Repeat] * Key.keystate
|
||||
| `Mouse of mouse
|
||||
| `Quit
|
||||
| `Fullscreen of bool
|
||||
@ -257,135 +62,14 @@ module Event = struct
|
||||
| `Fullscreen b -> F.str "`Fullscreen %b" b
|
||||
| `Unknown s -> F.str "`Unknown %s" s
|
||||
|
||||
let sdlkey_map = Hashtbl.create 1024
|
||||
|
||||
let () =
|
||||
let aa (x : int) (y : Key.code) = Hashtbl.add sdlkey_map x y in
|
||||
let open Sdl.K in
|
||||
aa return `Enter ;
|
||||
aa escape `Escape ;
|
||||
aa backspace `Backspace ;
|
||||
aa tab `Tab ;
|
||||
aa f1 (`Function 1) ;
|
||||
aa f2 (`Function 2) ;
|
||||
aa f3 (`Function 3) ;
|
||||
aa f4 (`Function 4) ;
|
||||
aa f5 (`Function 5) ;
|
||||
aa f6 (`Function 6) ;
|
||||
aa f7 (`Function 7) ;
|
||||
aa f8 (`Function 8) ;
|
||||
aa f9 (`Function 9) ;
|
||||
aa f10 (`Function 10) ;
|
||||
aa f11 (`Function 11) ;
|
||||
aa f12 (`Function 12) ;
|
||||
aa insert `Insert ;
|
||||
aa delete `Delete ;
|
||||
aa home `Home ;
|
||||
aa kend `End ;
|
||||
aa pageup (`Page `Up) ;
|
||||
aa pagedown (`Page `Down) ;
|
||||
aa right (`Arrow `Right) ;
|
||||
aa left (`Arrow `Left) ;
|
||||
aa down (`Arrow `Down) ;
|
||||
aa up (`Arrow `Up)
|
||||
|
||||
let key_of_sdlkey ev =
|
||||
let (kc : Sdl.keycode) =
|
||||
Sdl.Event.get ev Sdl.Event.keyboard_keycode
|
||||
land lnot Sdl.K.scancode_mask in
|
||||
match (Hashtbl.find_opt sdlkey_map kc, Uchar.is_valid kc) with
|
||||
| Some s, _ -> Some s
|
||||
| None, true -> Some (`Uchar (Uchar.of_int kc))
|
||||
| None, false -> None
|
||||
|
||||
let event_of_sdlevent ev : t option =
|
||||
match Sdl.Event.enum (Sdl.Event.get ev Sdl.Event.typ) with
|
||||
| (`Key_down | `Key_up) as d -> (
|
||||
match key_of_sdlkey ev with
|
||||
| None -> None
|
||||
| Some code ->
|
||||
let km = Sdl.Event.get ev Sdl.Event.keyboard_keymod in
|
||||
Some
|
||||
(`Key
|
||||
( ( match d with
|
||||
| _
|
||||
when Sdl.Event.get ev Sdl.Event.keyboard_repeat > 1
|
||||
->
|
||||
`Repeat
|
||||
| `Key_up -> `Release
|
||||
| _ -> `Press )
|
||||
, { code
|
||||
; ctrl= km land Sdl.Kmod.ctrl > 0
|
||||
; meta= km land Sdl.Kmod.alt > 0
|
||||
; super= km land Sdl.Kmod.gui > 0
|
||||
; shift= km land Sdl.Kmod.shift > 0 } ) ) )
|
||||
| `Mouse_motion ->
|
||||
let x, y = snd (Tsdl.Sdl.get_mouse_state ()) in
|
||||
Some (`Mouse (V2.v (float x) (float y)))
|
||||
| `Quit -> Some `Quit
|
||||
(* Unhandled events *)
|
||||
| `Text_editing -> Some (`Unknown "`Text_editing")
|
||||
| `Text_input -> Some (`Unknown "`Text_input")
|
||||
| `App_did_enter_background ->
|
||||
Some (`Unknown "`App_did_enter_background")
|
||||
| `App_did_enter_foreground ->
|
||||
Some (`Unknown "`App_did_enter_foreground ")
|
||||
| `App_low_memory -> Some (`Unknown "`App_low_memory ")
|
||||
| `App_terminating -> Some (`Unknown "`App_terminating ")
|
||||
| `App_will_enter_background ->
|
||||
Some (`Unknown "`App_will_enter_background ")
|
||||
| `App_will_enter_foreground ->
|
||||
Some (`Unknown "`App_will_enter_foreground ")
|
||||
| `Clipboard_update -> Some (`Unknown "`Clipboard_update ")
|
||||
| `Controller_axis_motion ->
|
||||
Some (`Unknown "`Controller_axis_motion ")
|
||||
| `Controller_button_down ->
|
||||
Some (`Unknown "`Controller_button_down ")
|
||||
| `Controller_button_up -> Some (`Unknown "`Controller_button_up ")
|
||||
| `Controller_device_added ->
|
||||
Some (`Unknown "`Controller_device_added ")
|
||||
| `Controller_device_remapped ->
|
||||
Some (`Unknown "`Controller_device_remapped ")
|
||||
| `Controller_device_removed ->
|
||||
Some (`Unknown "`Controller_device_removed ")
|
||||
| `Dollar_gesture -> Some (`Unknown "`Dollar_gesture ")
|
||||
| `Dollar_record -> Some (`Unknown "`Dollar_record ")
|
||||
| `Drop_file -> Some (`Unknown "`Drop_file ")
|
||||
| `Finger_down -> Some (`Unknown "`Finger_down")
|
||||
| `Finger_motion -> Some (`Unknown "`Finger_motion ")
|
||||
| `Finger_up -> Some (`Unknown "`Finger_up ")
|
||||
| `Joy_axis_motion -> Some (`Unknown "`Joy_axis_motion ")
|
||||
| `Joy_ball_motion -> Some (`Unknown "`Joy_ball_motion ")
|
||||
| `Joy_button_down -> Some (`Unknown "`Joy_button_down ")
|
||||
| `Joy_button_up -> Some (`Unknown "`Joy_button_up ")
|
||||
| `Joy_device_added -> Some (`Unknown "`Joy_device_added ")
|
||||
| `Joy_device_removed -> Some (`Unknown "`Joy_device_removed ")
|
||||
| `Joy_hat_motion -> Some (`Unknown "`Joy_hat_motion ")
|
||||
| `Mouse_button_down -> Some (`Unknown "`Mouse_button_down ")
|
||||
| `Mouse_button_up -> Some (`Unknown "`Mouse_button_up")
|
||||
| `Mouse_wheel -> Some (`Unknown "`Mouse_wheel ")
|
||||
| `Multi_gesture -> Some (`Unknown "`Multi_gesture")
|
||||
| `Sys_wm_event -> Some (`Unknown "`Sys_wm_event ")
|
||||
| `Unknown e -> Some (`Unknown (Format.sprintf "`Unknown %d" e))
|
||||
| `User_event -> Some (`Unknown "`User_event ")
|
||||
| `Display_event -> Some (`Unknown "`Display_event ")
|
||||
| `Sensor_update -> Some (`Unknown "`Sensor_update ")
|
||||
| `Window_event -> Some (`Unknown "`Window_event ")
|
||||
|
||||
let key_up : Sdl.keycode = 0x40000052
|
||||
let key_down : Sdl.keycode = 0x40000051
|
||||
let key_left : Sdl.keycode = 0x40000050
|
||||
let key_right : Sdl.keycode = 0x4000004f
|
||||
let handle_keyevents (el : events) f = List.iter f el
|
||||
end
|
||||
|
||||
module Display = struct
|
||||
open Tgles2
|
||||
open Tsdl
|
||||
open Gg
|
||||
open Wall
|
||||
module I = Image
|
||||
module P = Path
|
||||
module NVG = Graphv_webgl
|
||||
module I = NVG.Image
|
||||
module P = NVG.Path
|
||||
|
||||
let ( >>>= ) x f =
|
||||
match x with Ok a -> f a | Error _ as result -> result
|
||||
@ -398,114 +82,36 @@ module Display = struct
|
||||
type state =
|
||||
{ box: box2
|
||||
(* This is cannonically box within which the next element should draw *)
|
||||
; time: float
|
||||
; wall: Wall.renderer }
|
||||
; renderer: NVG.t }
|
||||
|
||||
(* the box2 here is cannonically the place the returner drew
|
||||
(the Wall.image extents) *)
|
||||
type image = box2 * Wall.image
|
||||
type image = box2 * NVG.Image.image
|
||||
type pane = state -> state * image
|
||||
type actor = (Event.events -> pane Lwt.t) ref
|
||||
|
||||
let pane_empty s =
|
||||
(s, (Box2.of_pts (Box2.o s.box) (Box2.o s.box), Image.empty))
|
||||
|
||||
type frame =
|
||||
{ sdl_win: Sdl.window
|
||||
; gl: Sdl.gl_context
|
||||
; wall: Wall.renderer
|
||||
; mutable last_pane: pane
|
||||
; mutable quit: bool
|
||||
; mutable fullscreen: bool }
|
||||
|
||||
let ticks () = Int32.to_float (Sdl.get_ticks ()) /. 1000.
|
||||
(s, (Box2.of_pts (Box2.o s.box) (Box2.o s.box), I.dummy))
|
||||
|
||||
let on_failure ~cleanup result =
|
||||
(match result with Ok _ -> () | Error _ -> cleanup ()) ;
|
||||
result
|
||||
|
||||
let video_initialized = lazy (Sdl.init Sdl.Init.video)
|
||||
|
||||
let make_frame ?(title = "komm") ~w ~h () =
|
||||
Lazy.force video_initialized
|
||||
>>>= fun () ->
|
||||
Sdl.create_window ~w ~h title
|
||||
Sdl.Window.(
|
||||
opengl + allow_highdpi + resizable (*+ input_grabbed*))
|
||||
>>>= fun sdl_win ->
|
||||
Sdl.set_window_title sdl_win title ;
|
||||
ignore (Sdl.gl_set_swap_interval (-1)) ;
|
||||
ignore (Sdl.gl_set_attribute Sdl.Gl.stencil_size 1) ;
|
||||
on_failure
|
||||
( Sdl.gl_create_context sdl_win
|
||||
>>>= fun gl ->
|
||||
let wall =
|
||||
Wall.Renderer.create ~antialias:true ~stencil_strokes:true ()
|
||||
in
|
||||
Ok
|
||||
{ sdl_win
|
||||
; gl
|
||||
; wall
|
||||
; quit= false
|
||||
; fullscreen= false
|
||||
; last_pane= pane_empty } )
|
||||
~cleanup:(fun () -> Sdl.destroy_window sdl_win)
|
||||
|
||||
let handle_frame_events frame events =
|
||||
List.iter
|
||||
(fun (e : Event.t) ->
|
||||
match e with
|
||||
| `Quit -> frame.quit <- true
|
||||
| `Fullscreen a ->
|
||||
frame.fullscreen <- a ;
|
||||
ignore (Sdl.show_cursor (not frame.fullscreen) : _ result) ;
|
||||
ignore
|
||||
( Sdl.set_window_fullscreen frame.sdl_win
|
||||
( if frame.fullscreen then
|
||||
Sdl.Window.fullscreen_desktop
|
||||
else Sdl.Window.windowed )
|
||||
: _ result )
|
||||
| _ -> () )
|
||||
events
|
||||
|
||||
let draw_pane frame pane =
|
||||
let width, height = Sdl.gl_get_drawable_size frame.sdl_win in
|
||||
let _, (_, image) =
|
||||
pane
|
||||
{ box= Box2.v (P2.v 0. 0.) (P2.v (float width) (float height))
|
||||
; time= ticks ()
|
||||
; wall= frame.wall } in
|
||||
Sdl.gl_make_current frame.sdl_win frame.gl
|
||||
>>>= fun () ->
|
||||
Gl.viewport 0 0 width height ;
|
||||
Gl.clear_color 0.0 0.0 0.0 1.0 ;
|
||||
Gl.(
|
||||
clear
|
||||
(color_buffer_bit lor depth_buffer_bit lor stencil_buffer_bit)) ;
|
||||
Gl.enable Gl.blend ;
|
||||
Gl.blend_func_separate Gl.one Gl.src_alpha Gl.one
|
||||
Gl.one_minus_src_alpha ;
|
||||
Gl.enable Gl.cull_face_enum ;
|
||||
Gl.disable Gl.depth_test ;
|
||||
let width = float width and height = float height in
|
||||
Wall.Renderer.render frame.wall ~width ~height image ;
|
||||
Sdl.gl_swap_window frame.sdl_win ;
|
||||
let draw_pane vg pane width height =
|
||||
let _, (b, image) =
|
||||
pane {box= Box2.v (P2.v 0. 0.) (P2.v width height); renderer= vg}
|
||||
in
|
||||
let w, h = (Box2.w b, Box2.h b) in
|
||||
let paint =
|
||||
NVG.Paint.image_pattern vg ~cx:0. ~cy:0. ~w ~h ~angle:0. ~image
|
||||
~alpha:1. in
|
||||
NVG.set_fill_paint vg ~paint ;
|
||||
NVG.fill vg ;
|
||||
Ok ()
|
||||
|
||||
let rec get_events () : Event.t list =
|
||||
(* create and fill event list *)
|
||||
let ev = Sdl.Event.create () in
|
||||
if Sdl.poll_event (Some ev) then
|
||||
match Event.event_of_sdlevent ev with
|
||||
| Some e -> get_events () @ [e]
|
||||
| None -> get_events ()
|
||||
else []
|
||||
|
||||
let successful_actor = ref (fun _ -> Lwt.return pane_empty)
|
||||
|
||||
let display_frame frame (actor : actor) =
|
||||
let events = get_events () in
|
||||
handle_frame_events frame events ;
|
||||
let render (vg : NVG.t) (w : float) (h : float) (actor : actor) =
|
||||
if List.length events > 0 then (
|
||||
(* recompute the actor definition with the new events to return a new pane *)
|
||||
( try
|
||||
@ -521,31 +127,11 @@ module Display = struct
|
||||
actor := !successful_actor ;
|
||||
!actor events )
|
||||
>>= fun p ->
|
||||
frame.last_pane <- p ;
|
||||
(* call draw_pane because we should redraw now that we have updated *)
|
||||
ignore (draw_pane frame frame.last_pane) ;
|
||||
ignore (draw_pane vg p w h) ;
|
||||
Lwt.return_unit )
|
||||
else Lwt.return_unit
|
||||
|
||||
let run frame actor () =
|
||||
let frame = get_result frame in
|
||||
Sdl.show_window frame.sdl_win ;
|
||||
let rec loop () =
|
||||
Lwt.pause () (* seems required for the irc connection to work *)
|
||||
>>= fun () ->
|
||||
Lwt_unix.sleep 0.030
|
||||
>>= fun () ->
|
||||
display_frame frame actor
|
||||
>>= fun () ->
|
||||
if not frame.quit then loop () else Lwt.return_unit in
|
||||
Lwt_main.run (loop ()) ;
|
||||
print_endline "quit" ;
|
||||
Sdl.hide_window frame.sdl_win ;
|
||||
Sdl.gl_delete_context frame.gl ;
|
||||
Sdl.destroy_window frame.sdl_win ;
|
||||
Sdl.quit () ;
|
||||
()
|
||||
|
||||
let gray ?(a = 1.0) v = Color.v v v v a
|
||||
|
||||
module FontCache = Map.Make (String)
|
||||
@ -600,9 +186,7 @@ module Display = struct
|
||||
(Box2.oy b) (Box2.maxx b) (Box2.maxy b)
|
||||
|
||||
let draw_label text b =
|
||||
let f =
|
||||
Wall_text.Font.make ~size:(Box2.h b) (Lazy.force font_sans)
|
||||
in
|
||||
let f = NVG.Font.make ~size:(Box2.h b) (Lazy.force font_sans) in
|
||||
( Box2.v (Box2.o b)
|
||||
(P2.v (Wall_text.Font.text_width f text) (Box2.h b))
|
||||
, I.paint
|
||||
|
||||
Reference in New Issue
Block a user