basic cursor works but getting text editing is hard, might go in a different direction

This commit is contained in:
cqc
2024-04-28 12:30:57 -05:00
parent 54e9cc90d3
commit 11806042fe
4 changed files with 580 additions and 359 deletions

2
dune
View File

@ -15,7 +15,7 @@
(executables (executables
(names oplevel) (names oplevel)
(modules oplevel secrets perfgraph ogui) (modules oplevel secrets perfgraph ogui glfw_types)
(libraries (libraries
lwt lwt
store store

149
glfw_types.ml Normal file
View File

@ -0,0 +1,149 @@
open GLFW
module F = Fmt
let pp_key : GLFW.key F.t =
fun ppf k ->
F.pf ppf
GLFW.(
match k with
| Unknown -> "Unknown"
| Space -> "Space"
| Apostrophe -> "Apostrophe"
| Comma -> "Comma"
| Minus -> "Minus"
| Period -> "Period"
| Slash -> "Slash"
| Num0 -> "Num0"
| Num1 -> "Num1"
| Num2 -> "Num2"
| Num3 -> "Num3"
| Num4 -> "Num4"
| Num5 -> "Num5"
| Num6 -> "Num6"
| Num7 -> "Num7"
| Num8 -> "Num8"
| Num9 -> "Num9"
| Semicolon -> "Semicolon"
| Equal -> "Equal"
| A -> "A"
| B -> "B"
| C -> "C"
| D -> "D"
| E -> "E"
| F -> "F"
| G -> "G"
| H -> "H"
| I -> "I"
| J -> "J"
| K -> "K"
| L -> "L"
| M -> "M"
| N -> "N"
| O -> "O"
| P -> "P"
| Q -> "Q"
| R -> "R"
| S -> "S"
| T -> "T"
| U -> "U"
| V -> "V"
| W -> "W"
| X -> "X"
| Y -> "Y"
| Z -> "Z"
| LeftBracket -> "LeftBracket"
| Backslash -> "Backslash"
| RightBracket -> "RightBracket"
| GraveAccent -> "GraveAccent"
| World1 -> "World1"
| World2 -> "World2"
| Escape -> "Escape"
| Enter -> "Enter"
| Tab -> "Tab"
| Backspace -> "Backspace"
| Insert -> "Insert"
| Delete -> "Delete"
| Right -> "Right"
| Left -> "Left"
| Down -> "Down"
| Up -> "Up"
| PageUp -> "PageUp"
| PageDown -> "PageDown"
| Home -> "Home"
| End -> "End"
| CapsLock -> "CapsLock"
| ScrollLock -> "ScrollLock"
| NumLock -> "NumLock"
| PrintScreen -> "PrintScreen"
| Pause -> "Pause"
| F1 -> "F1"
| F2 -> "F2"
| F3 -> "F3"
| F4 -> "F4"
| F5 -> "F5"
| F6 -> "F6"
| F7 -> "F7"
| F8 -> "F8"
| F9 -> "F9"
| F10 -> "F10"
| F11 -> "F11"
| F12 -> "F12"
| F13 -> "F13"
| F14 -> "F14"
| F15 -> "F15"
| F16 -> "F16"
| F17 -> "F17"
| F18 -> "F18"
| F19 -> "F19"
| F20 -> "F20"
| F21 -> "F21"
| F22 -> "F22"
| F23 -> "F23"
| F24 -> "F24"
| F25 -> "F25"
| Kp0 -> "Kp0"
| Kp1 -> "Kp1"
| Kp2 -> "Kp2"
| Kp3 -> "Kp3"
| Kp4 -> "Kp4"
| Kp5 -> "Kp5"
| Kp6 -> "Kp6"
| Kp7 -> "Kp7"
| Kp8 -> "Kp8"
| Kp9 -> "Kp9"
| KpDecimal -> "KpDecimal"
| KpDivide -> "KpDivide"
| KpMultiply -> "KpMultiply"
| KpSubtract -> "KpSubtract"
| KpAdd -> "KpAdd"
| KpEnter -> "KpEnter"
| KpEqual -> "KpEqual"
| LeftShift -> "LeftShift"
| LeftControl -> "LeftControl"
| LeftAlt -> "LeftAlt"
| LeftSuper -> "LeftSuper"
| RightShift -> "RightShift"
| RightControl -> "RightControl"
| RightAlt -> "RightAlt"
| RightSuper -> "RightSuper"
| Menu -> "Menu")
let pp_key_action : GLFW.key_action F.t =
fun ppf s ->
F.pf ppf
GLFW.(
match s with
| Release -> "Release"
| Press -> "Press"
| Repeat -> "Repeat")
let pp_mods =
F.(
list (fun ppf s ->
pf ppf
GLFW.(
match s with
| Shift -> "Shift"
| Control -> "Control"
| Alt -> "Alt"
| Super -> "Super")))

570
ogui.ml
View File

@ -1,3 +1,4 @@
open Lwt.Infix
module Gv = Graphv_gles2_native module Gv = Graphv_gles2_native
module F = Fmt module F = Fmt
@ -26,6 +27,15 @@ end
type margin = Margin.t type margin = Margin.t
let string_of_utf_8_uchar uc =
Buffer.(
let b = create 4 in
add_utf_8_uchar b uc;
contents b)
let pp_uchar : Uchar.t F.t =
fun ppf u -> F.pf ppf "%s" (string_of_utf_8_uchar u)
module Sense = struct module Sense = struct
type t = { type t = {
click : bool; click : bool;
@ -41,25 +51,195 @@ module Sense = struct
{ click = false; drag = false; focusable = true; edit = false } { click = false; drag = false; focusable = true; edit = false }
end end
module EventFilter = struct module TextBuffer = struct
type t = { type t =
tab : bool; | Tree of {
horizontal_arrrows : bool; mutable path : string list;
vertical_arrows : bool; mutable tree : Store.S.tree;
escape : bool; repo : Store.Sync.db;
} }
| Buffer of { name : string; buf : Buffer.t }
let default = let of_repo ~path ~(repo : Store.Sync.db) =
{ let tree = Lwt_main.run ((fun () -> Store.S.tree repo) ()) in
tab = false; Tree { path; tree; repo }
horizontal_arrrows = false;
vertical_arrows = false; let buffer ~name ~buf = Buffer { name; buf }
escape = false;
} let insert_uchar t n uc : t Lwt.t =
F.epr "TextBuffer.insert_uchar %d %a@." n pp_uchar uc;
match t with
| Tree ({ path; tree; _ } as tt) ->
Store.S.Tree.update tree path (function
| Some src ->
assert (n <= String.length src);
let ucbuf = Bytes.create 8 in
let uclen = Bytes.set_utf_8_uchar ucbuf 0 uc in
let dst = Bytes.create (String.length src + uclen) in
BytesLabels.blit_string ~src ~src_pos:0 ~dst ~dst_pos:0
~len:n;
BytesLabels.blit ~src:ucbuf ~src_pos:0 ~dst ~dst_pos:n
~len:uclen;
BytesLabels.blit_string ~src ~src_pos:n ~dst
~dst_pos:(n + uclen)
~len:(String.length src - (n + uclen));
Some (Bytes.to_string dst)
| None -> None)
>>= fun tree -> Lwt.return (Tree { tt with tree })
| Buffer { buf; _ } as b ->
let textend = Buffer.sub buf n (Buffer.length buf - n) in
Buffer.truncate buf n;
Buffer.add_utf_8_uchar buf uc;
Buffer.add_string buf textend;
Lwt.return b
let contents = function
| Tree { path; tree; _ } ->
(try Store.S.Tree.get tree path with
| Not_found | Invalid_argument _ ->
Lwt.return
@@ F.str
"print_newline \"/%s: Not_found | \
Invalid_argument\";;"
(String.concat "/" path)
| exc ->
Lwt.return
(F.str "Store.S.Tree.get /%s exception: %s"
(String.concat "/" path)
(Printexc.to_string exc)))
>>= fun text -> Lwt.return text
| Buffer { buf; _ } -> Lwt.return (Buffer.contents buf)
let length = function
| Tree { path; tree; _ } ->
Store.S.Tree.get tree path >>= fun text ->
Lwt.return (String.length text)
| Buffer { buf; _ } -> Lwt.return @@ Buffer.length buf
end end
module Event = struct
type key_action = GLFW.key_action
type key = GLFW.key
type key_mod = GLFW.key_mod
type event = Key of key_action * key * key_mod list
(* Stolen from zed_input.ml *)
module EventMap = Map.Make (struct
type t = event
let compare = compare
end)
type 'a t = 'a node EventMap.t
and 'a node = Set of 'a t | Val of 'a
let empty = EventMap.empty
let rec add (events : event list) value set =
match events with
| [] -> invalid_arg "Event.add"
| [ event ] -> EventMap.add event (Val value) set
| event :: events -> (
match
try Some (EventMap.find event set) with Not_found -> None
with
| None | Some (Val _) ->
EventMap.add event (Set (add events value empty)) set
| Some (Set s) ->
EventMap.add event (Set (add events value s)) set)
let adds (events : event list list) value set =
List.fold_left (fun s e -> add e value s) set events
let rec remove events set =
match events with
| [] -> invalid_arg "Event.remove"
| [ event ] -> EventMap.remove event set
| event :: events -> (
match
try Some (EventMap.find event set) with Not_found -> None
with
| None | Some (Val _) -> set
| Some (Set s) ->
let s = remove events s in
if EventMap.is_empty s then EventMap.remove event set
else EventMap.add event (Set s) set)
let fold f set acc =
let rec loop prefix set acc =
EventMap.fold
(fun event node acc ->
match node with
| Val v -> f (List.rev (event :: prefix)) v acc
| Set s -> loop (event :: prefix) s acc)
set acc
in
loop [] set acc
let bindings set =
List.rev
(fold (fun events action l -> (events, action) :: l) set [])
module type Pack = sig
type a
type b
val set : a t
val map : a -> b
end
type 'a pack = (module Pack with type b = 'a)
type 'a resolver = 'a pack list
let pack (type u v) map set =
let module Pack = struct
type a = u
type b = v
let set = set
let map = map
end in
(module Pack : Pack with type b = v)
let resolver l = l
type 'a result =
| Accepted of 'a
| Continue of 'a resolver
| Rejected
let rec resolve_rec :
'a. event -> 'a pack list -> 'a pack list -> 'a result =
fun (type u) event acc packs ->
match packs with
| [] -> if acc = [] then Rejected else Continue (List.rev acc)
| p :: packs -> (
let module Pack = (val p : Pack with type b = u) in
match
try Some (EventMap.find event Pack.set)
with Not_found -> None
with
| Some (Set set) ->
resolve_rec event (pack Pack.map set :: acc) packs
| Some (Val v) -> Accepted (Pack.map v)
| None -> resolve_rec event acc packs)
let resolve event sets = resolve_rec event [] sets
include Glfw_types
let pp_event : event F.t =
fun ppf e ->
let open Glfw_types in
match e with
| Key (a, k, m) ->
F.pf ppf "Key %a, %a, %a" pp_key_action a pp_key k pp_mods m
(* | Char u -> F.pf ppf "Char %a" pp_uchar u
| AnyChar -> F.pf ppf "AnyChar" *)
end
type event = Event.event
type id = int type id = int
type event_filter = EventFilter.t
module Context = struct module Context = struct
type t = { derp : bool } type t = { derp : bool }
@ -250,7 +430,7 @@ module TextLayout = struct
} }
type layout_job = { type layout_job = {
text : string; text : TextBuffer.t;
sections : layout_section array; sections : layout_section array;
wrap : text_wrapping; wrap : text_wrapping;
first_row_min_height : float; first_row_min_height : float;
@ -264,7 +444,9 @@ module TextLayout = struct
F.( F.(
record record
[ [
field "text" (fun s -> String.length s.text) int; field "text"
(fun s -> Lwt_main.run (TextBuffer.length s.text))
int;
field "sections" field "sections"
(fun s -> s.sections) (fun s -> s.sections)
(array pp_layout_section); (array pp_layout_section);
@ -279,7 +461,9 @@ module TextLayout = struct
let default_layout_job () = let default_layout_job () =
{ {
text = ""; text =
TextBuffer.buffer ~name:"default_layout_job"
~buf:(Buffer.create 32);
sections = Array.make 0 layout_section_default; sections = Array.make 0 layout_section_default;
wrap = default_text_wrapping (); wrap = default_text_wrapping ();
first_row_min_height = 0.0; first_row_min_height = 0.0;
@ -461,6 +645,7 @@ module TextLayout = struct
match c with match c with
| Some (a, b) -> | Some (a, b) ->
let a', b' = f a.index b.index in let a', b' = f a.index b.index in
F.epr "cursor_state_update %d %d@." a' b';
Some (new_cursor_range (max a' 0, max b' 0)) Some (new_cursor_range (max a' 0, max b' 0))
| None -> None | None -> None
@ -473,7 +658,7 @@ module TextLayout = struct
Array.make 1 Array.make 1
{ {
leading_space = 0.0; leading_space = 0.0;
byte_range = (0, String.length text); byte_range = (0, Lwt_main.run (TextBuffer.length text));
format; format;
}; };
wrap = wrap =
@ -481,7 +666,8 @@ module TextLayout = struct
break_on_newline = true; break_on_newline = true;
} }
let simple text (format : text_format) wrap_width : layout_job = let simple (text : TextBuffer.t) (format : text_format) wrap_width :
layout_job =
{ {
(default_layout_job ()) with (default_layout_job ()) with
text; text;
@ -489,7 +675,7 @@ module TextLayout = struct
Array.make 1 Array.make 1
{ {
leading_space = 0.0; leading_space = 0.0;
byte_range = (0, String.length text); byte_range = (0, Lwt_main.run (TextBuffer.length text));
format; format;
}; };
wrap = wrap =
@ -497,7 +683,8 @@ module TextLayout = struct
break_on_newline = true; break_on_newline = true;
} }
let simple_singleline text (format : text_format) : layout_job = let simple_singleline (text : TextBuffer.t) (format : text_format) :
layout_job =
{ {
(default_layout_job ()) with (default_layout_job ()) with
text; text;
@ -505,7 +692,7 @@ module TextLayout = struct
Array.make 1 Array.make 1
{ {
leading_space = 0.0; leading_space = 0.0;
byte_range = (0, String.length text); byte_range = (0, Lwt_main.run (TextBuffer.length text));
format; format;
}; };
wrap = default_text_wrapping (); wrap = default_text_wrapping ();
@ -585,7 +772,8 @@ module TextLayout = struct
let lines = Gv.Text.make_empty_rows job.wrap.max_rows in let lines = Gv.Text.make_empty_rows job.wrap.max_rows in
let row_count = let row_count =
Gv.Text.break_lines gv ~break_width:job.wrap.max_width Gv.Text.break_lines gv ~break_width:job.wrap.max_width
~max_rows:job.wrap.max_rows ~lines job.text ~max_rows:job.wrap.max_rows ~lines
(Lwt_main.run (TextBuffer.contents job.text))
in in
(* F.epr "row_count=%d@." row_count; *) (* F.epr "row_count=%d@." row_count; *)
let height = ref (V2.y pos) in let height = ref (V2.y pos) in
@ -654,62 +842,64 @@ module Painter = struct
(* F.epr "g.rows=%a@." F.(braces (array TextLayout.pp_row)) g.rows;*) (* F.epr "g.rows=%a@." F.(braces (array TextLayout.pp_row)) g.rows;*)
Array.iter Array.iter
Gv.( (fun (row : TextLayout.row) ->
fun (row : TextLayout.row) -> let sections =
let sections = List.filter
List.filter (fun (r : TextLayout.layout_section) ->
(fun (r : TextLayout.layout_section) -> fst r.byte_range <= row.text_row.end_index
fst r.byte_range <= row.text_row.end_index && snd r.byte_range > row.text_row.start_index)
&& snd r.byte_range > row.text_row.start_index) (Array.to_list
(Array.to_list @@ Array.sub g.job.sections row.section_index_at_start
@@ Array.sub g.job.sections row.section_index_at_start (Array.length g.job.sections
(Array.length g.job.sections - row.section_index_at_start))
- row.section_index_at_start)) in
in assert (List.length sections > 0);
assert (List.length sections > 0);
ignore ignore
Gg.( Gg.(
List.fold_left List.fold_left
(fun x (sec : TextLayout.layout_section) -> (fun x (sec : TextLayout.layout_section) ->
let start, end_ = let start, end_ =
( min ( min
(String.length g.job.text - 1) (Lwt_main.run (TextBuffer.length g.job.text) - 1)
(max 0 (max 0
(max (fst sec.byte_range) (max (fst sec.byte_range)
row.text_row.start_index)), row.text_row.start_index)),
min min
(String.length g.job.text - 1) (Lwt_main.run (TextBuffer.length g.job.text) - 1)
(max 0 (max 0
(min (snd sec.byte_range) (min (snd sec.byte_range)
row.text_row.end_index)) ) row.text_row.end_index)) )
in in
let font_name, font_size = let font_name, font_size =
match sec.format.font_id with match sec.format.font_id with
| Default -> ("mono", 18.) | Default -> ("mono", 18.)
| FontId (s, size) -> (s, size) | FontId (s, size) -> (s, size)
in in
Text.set_font_face t ~name:font_name; let open Gv in
Text.set_size t ~size:font_size; Text.set_font_face t ~name:font_name;
Text.set_align t ~align:Align.(left lor top); Text.set_size t ~size:font_size;
let metrics = Gv.Text.metrics t in Text.set_align t ~align:Align.(left lor top);
let bounds = let metrics = Gv.Text.metrics t in
Gv.Text.bounds t ~x ~y:0. ~start ~end_ g.job.text let bounds =
in Gv.Text.bounds t ~x ~y:0. ~start ~end_
Path.begin_ t; (Lwt_main.run (TextBuffer.contents g.job.text))
Path.rect t ~x ~y:(Box2.miny row.rect) in
~w:bounds.advance ~h:metrics.line_height; Path.begin_ t;
set_fill_color t ~color:sec.format.background; Path.rect t ~x ~y:(Box2.miny row.rect)
fill t; ~w:bounds.advance ~h:metrics.line_height;
set_fill_color t ~color:sec.format.background;
fill t;
set_fill_color t ~color:sec.format.color; set_fill_color t ~color:sec.format.color;
let w = let w =
Text.text_w t ~x ~y:(Box2.miny row.rect) ~start Text.text_w t ~x ~y:(Box2.miny row.rect) ~start
~end_ g.job.text ~end_
in (Lwt_main.run (TextBuffer.contents g.job.text))
w) in
(Box2.minx row.rect) sections)) w)
(Box2.minx row.rect) sections))
g.rows g.rows
end end
@ -877,25 +1067,18 @@ module Placer = struct
end end
module Ui = struct module Ui = struct
type key_callback = type t = {
t ->
GLFW.window ->
GLFW.key ->
int ->
GLFW.key_action ->
GLFW.key_mod list ->
unit
and t = {
id : id; id : id;
style : Style.t; style : Style.t;
placer : Placer.t; placer : Placer.t;
enabled : bool; enabled : bool;
gv : Gv.t; gv : Gv.t;
glfw_window : GLFW.window option; glfw_window : GLFW.window option;
mutable key : key_callback; mutable bindings : action list Event.t;
} }
and action = Custom of (unit -> unit Lwt.t)
let id = ref 0 let id = ref 0
let spacing ui = ui.style.spacing let spacing ui = ui.style.spacing
let available_size ui = Placer.available_size ui.placer let available_size ui = Placer.available_size ui.placer
@ -907,7 +1090,6 @@ module Ui = struct
(!id, size) (!id, size)
let cursor_origin (ui : t) = Gg.Box2.o ui.placer.region.max_rect let cursor_origin (ui : t) = Gg.Box2.o ui.placer.region.max_rect
let key_callback_default _ _ _ _ _ _ = ()
let window gv ?(window : GLFW.window option) rect : t = let window gv ?(window : GLFW.window option) rect : t =
let id, rect = allocate_space gv rect in let id, rect = allocate_space gv rect in
@ -920,56 +1102,57 @@ module Ui = struct
enabled = true; enabled = true;
gv; gv;
glfw_window = window; glfw_window = window;
key = key_callback_default; bindings = Event.empty;
} }
let keycallback t window key int state mods : unit = let callback_resolver : action list Event.resolver option ref =
t.key t window key int state mods ref Option.None
let add_key_callback (t : t) ~(f : key_callback) : unit = let keycallback t (state : Event.key_action) (key : Event.key)
let g = t.key in (mods : Event.key_mod list) : unit =
t.key <- let res =
(fun a1 a2 a3 a4 a5 a6 -> match !callback_resolver with
f a1 a2 a3 a4 a5 a6; | Some res -> res
g a1 a2 a3 a4 a5 a6) | None -> Event.resolver [ Event.pack Fun.id t.bindings ]
in
let interact (_ui : t) rect (id : id option) sense : Response.t = Event.(
let id = Option.value id ~default:(-1) in F.epr "Ui.keycallback %a %a %a@." pp_key key pp_key_action state
{ pp_mods mods);
ctx = { derp = false }; ignore
id; @@ Lwt_main.run
rect; ((fun () : bool Lwt.t ->
interact_rect = rect; match Event.resolve (Key (state, key, mods)) res with
sense; | Event.Accepted actions ->
enabled = true; callback_resolver := None;
contains_pointer = (*TODO*) true; let rec exec : action list -> bool Lwt.t = function
hovered = (*TODO*) true; | Custom f :: actions ->
highlighted = (*TODO*) true; f () >>= fun () -> exec actions
clicked = (*TODO*) true; | [] -> Lwt.return false
fake_primary_click = (*TODO*) false; in
long_touched = (*TODO*) false; exec actions
drag_started = false; | Event.Continue res ->
dragged = false; callback_resolver := Some res;
drag_stopped = false; Lwt.return true
is_pointer_button_down_on = false; | Event.Rejected ->
interact_pointer_pos = None; callback_resolver := None;
changed = false; Lwt.return false)
} ())
end
module TextBuffer = struct let chrcallback_ref : (Uchar.t -> unit Lwt.t) ref =
type t = String of string ref (fun c ->
F.epr "chrcallback: '%a'@." pp_uchar c;
Lwt.return_unit)
let is_mutable = function String _ -> true let chrcallback _t (chr : int) : unit =
let as_string = function String s -> s Lwt_main.run @@ !chrcallback_ref @@ Uchar.of_int chr
end end
module TextEdit = struct module TextEdit = struct
open Gg open Gg
type t = { type t = {
text : TextBuffer.t; mutable text : TextBuffer.t;
hint_text : TextLayout.widget_text;
id : id option; id : id option;
id_source : id option; id_source : id option;
text_format : TextLayout.text_format; text_format : TextLayout.text_format;
@ -982,7 +1165,6 @@ module TextEdit = struct
interactive : bool; interactive : bool;
desired_width : float option; desired_width : float option;
desired_height_rows : int; desired_height_rows : int;
event_filter : event_filter;
cursor_at_end : bool; cursor_at_end : bool;
min_size : Gg.v2; min_size : Gg.v2;
align : align; align : align;
@ -990,7 +1172,7 @@ module TextEdit = struct
char_limit : int; (* return_key : keyboard_shortcut; *) char_limit : int; (* return_key : keyboard_shortcut; *)
} }
type state = { and state = {
mutable cursor : TextLayout.cursor_state; mutable cursor : TextLayout.cursor_state;
(* undoer : undoer; *) (* undoer : undoer; *)
singleline_offset : float; singleline_offset : float;
@ -999,27 +1181,81 @@ module TextEdit = struct
let state_mem : (int * state) list ref = ref [] let state_mem : (int * state) list ref = ref []
let process_key (state : state) (key : GLFW.key) let add_bindings (t : t) (ui : Ui.t) (state : state) : unit Lwt.t =
(action : GLFW.key_action) (mods : GLFW.key_mod list) : unit =
let open GLFW in let open GLFW in
match (action, key, mods) with let open Event in
| Press, F, [ Control ] | Press, Right, [] -> let open Ui in
state.cursor <- ui.bindings <-
TextLayout.cursor_state_update empty
~f:(fun a b -> |> adds
F.epr "cursor_state_update %d %d@." a b; [
(a + 1, b + 1)) [ Key (Press, F, [ Control ]) ];
state.cursor [ Key (Press, Right, []) ];
| Press, B, [ Control ] | Press, Left, [] -> ]
state.cursor <- [
TextLayout.cursor_state_update Custom
~f:(fun a b -> (fun () ->
F.epr "cursor_state_update %d %d@." a b; state.cursor <-
(a - 1, b - 1)) TextLayout.cursor_state_update
state.cursor ~f:(fun a b -> (a + 1, b + 1))
| _ -> () state.cursor;
Lwt.return_unit);
]
|> adds
[
[ Key (Press, B, [ Control ]) ];
[ Key (Press, Left, []) ];
]
[
Custom
(fun () ->
state.cursor <-
TextLayout.cursor_state_update
~f:(fun a b -> (a - 1, b - 1))
state.cursor;
Lwt.return_unit);
]
|> adds
[
[ Key (Press, N, [ Control ]) ];
[ Key (Press, Down, []) ];
]
[
Custom
(fun () ->
state.cursor <-
TextLayout.cursor_state_update
~f:(fun a b -> (a - 1, b - 1))
state.cursor;
Lwt.return_unit);
]
|> adds
[
[ Key (Press, P, [ Control ]) ]; [ Key (Press, Up, []) ];
]
[
Custom
(fun () ->
state.cursor <-
TextLayout.cursor_state_update
~f:(fun a b -> (a - 1, b - 1))
state.cursor;
Lwt.return_unit);
];
(* WARN XXX TKTK TODO this is probably "breaking" the lwt context and being used in other calls to Lwt_main.run *)
(Ui.chrcallback_ref :=
fun c ->
match state.cursor with
| Some (_a, b) ->
TextBuffer.insert_uchar t.text b.index c >>= fun text ->
t.text <- text;
Lwt.return_unit
| None -> Lwt.return_unit
(* This creates a giant stack of calls lol
>>= fun () -> !Ui.chrcallback_ref c *));
Lwt.return_unit
let load_state ui id = let load_state id =
match List.assoc_opt id !state_mem with match List.assoc_opt id !state_mem with
| Some state -> state | Some state -> state
| None -> | None ->
@ -1031,14 +1267,10 @@ module TextEdit = struct
} }
in in
state_mem := (id, state) :: !state_mem; state_mem := (id, state) :: !state_mem;
(* We use this as the one shot for registering keyboard shortcuts
since they just operate on the state *)
Ui.add_key_callback ui ~f:(fun _ _ key _ action mods ->
process_key state key action mods);
state state
type output = { type output = {
response : Response.t; (* response : Response.t; *)
galley : TextLayout.galley; galley : TextLayout.galley;
galley_pos : Gg.p2; galley_pos : Gg.p2;
text_clip_rect : Gg.box2; text_clip_rect : Gg.box2;
@ -1050,7 +1282,6 @@ module TextEdit = struct
(text : TextBuffer.t) : t = (text : TextBuffer.t) : t =
{ {
text; text;
hint_text = RichText TextLayout.rich_text_default;
id = None; id = None;
id_source = None; id_source = None;
text_format; text_format;
@ -1062,13 +1293,6 @@ module TextEdit = struct
interactive = true; interactive = true;
desired_width = None; desired_width = None;
desired_height_rows = 4; desired_height_rows = 4;
event_filter =
{
EventFilter.default with
horizontal_arrrows = true;
vertical_arrows = true;
tab = false;
};
cursor_at_end = true; cursor_at_end = true;
min_size = Gg.V2.zero; min_size = Gg.V2.zero;
align = Min; align = Min;
@ -1078,6 +1302,8 @@ module TextEdit = struct
} }
let show_content (t : t) (ui : Ui.t) : output = let show_content (t : t) (ui : Ui.t) : output =
let state = load_state (Option.value ~default:(-1) t.id) in
Lwt_main.run (add_bindings t ui state);
let origin = Ui.cursor_origin ui in let origin = Ui.cursor_origin ui in
(* TODO .or(ui.visuals().override_text_color) *) (* TODO .or(ui.visuals().override_text_color) *)
(* let row_height = (Gv.Text.metrics ui.gv).line_height in *) (* let row_height = (Gv.Text.metrics ui.gv).line_height in *)
@ -1093,23 +1319,17 @@ module TextEdit = struct
available_width available_width
else Float.min desired_width available_width else Float.min desired_width available_width
in in
let state = load_state ui (Option.value ~default:(-1) t.id) in
let default_layouter (ui : Ui.t) (text : TextBuffer.t) let default_layouter (ui : Ui.t) (text : TextBuffer.t)
(wrap_width : size1) : TextLayout.galley = (wrap_width : size1) : TextLayout.galley =
let font = let font =
match Gv.Text.find_font ui.gv ~name:"mono" with match Gv.Text.find_font ui.gv ~name:"mono" with
| Some gv -> Fonts.{ gv; pixels_per_point = 1.0 } | Some gv -> Fonts.{ gv; pixels_per_point = 1.0 }
| None -> failwith "can't font font 'mono'" | None -> failwith "can't find font 'mono'"
in in
let layout_job = let layout_job =
if t.multiline then if t.multiline then
TextLayout.simple TextLayout.simple text t.text_format wrap_width
(TextBuffer.as_string text) else TextLayout.simple_singleline text t.text_format
t.text_format wrap_width
else
TextLayout.simple_singleline
(TextBuffer.as_string text)
t.text_format
in in
let layout_job = let layout_job =
TextLayout.with_cursor TextLayout.with_cursor
@ -1145,9 +1365,9 @@ module TextEdit = struct
(* TODO state = TextEditState::load(ui.ctx(), id)... *) (* TODO state = TextEditState::load(ui.ctx(), id)... *)
(* TODO moved up let state = load_state (Option.value ~default:(-1) t.id) in *) (* TODO moved up let state = load_state (Option.value ~default:(-1) t.id) in *)
(* TODO allow_drag_to_select = ... *) (* TODO allow_drag_to_select = ... *)
let sense = if t.interactive then Sense.click else Sense.hover in let _sense = if t.interactive then Sense.click else Sense.hover in
let response = Ui.interact ui outer_rect t.id sense in (* let response = Ui.interact ui outer_rect t.id sense in *)
(* TODO *) (* TODO *)
let text_clip_rect = rect in let text_clip_rect = rect in
(* let painter = Ui.painter_at ui text_clip_rect in *) (* let painter = Ui.painter_at ui text_clip_rect in *)
@ -1162,7 +1382,6 @@ module TextEdit = struct
let _align_offset = rect in let _align_offset = rect in
{ {
response;
galley; galley;
galley_pos = Box2.o galley_pos; galley_pos = Box2.o galley_pos;
text_clip_rect; text_clip_rect;
@ -1171,11 +1390,8 @@ module TextEdit = struct
} }
let show (t : t) ui : output = let show (t : t) ui : output =
let _is_mutable = TextBuffer.is_mutable t.text in
let _frame = t.frame in
let _margin = t.margin in let _margin = t.margin in
let output = show_content t ui in let output = show_content t ui in
let _outer_rect = output.response.rect in (* let _outer_rect = output.response.rect in *)
output output
end end

View File

@ -1,4 +1,3 @@
open Lwt.Infix
module F = Fmt module F = Fmt
open Tgles2 open Tgles2
module Gv = Graphv_gles2_native module Gv = Graphv_gles2_native
@ -37,153 +36,6 @@ let load_data vg =
Gv.Text.add_fallback vg ~name:"sans-bold" ~fallback:"emoji"; Gv.Text.add_fallback vg ~name:"sans-bold" ~fallback:"emoji";
Gv.Text.set_font_face vg ~name:"mono" Gv.Text.set_font_face vg ~name:"mono"
let pp_glfw_key : GLFW.key F.t =
fun ppf k ->
F.pf ppf
GLFW.(
match k with
| Unknown -> "Unknown"
| Space -> "Space"
| Apostrophe -> "Apostrophe"
| Comma -> "Comma"
| Minus -> "Minus"
| Period -> "Period"
| Slash -> "Slash"
| Num0 -> "Num0"
| Num1 -> "Num1"
| Num2 -> "Num2"
| Num3 -> "Num3"
| Num4 -> "Num4"
| Num5 -> "Num5"
| Num6 -> "Num6"
| Num7 -> "Num7"
| Num8 -> "Num8"
| Num9 -> "Num9"
| Semicolon -> "Semicolon"
| Equal -> "Equal"
| A -> "A"
| B -> "B"
| C -> "C"
| D -> "D"
| E -> "E"
| F -> "F"
| G -> "G"
| H -> "H"
| I -> "I"
| J -> "J"
| K -> "K"
| L -> "L"
| M -> "M"
| N -> "N"
| O -> "O"
| P -> "P"
| Q -> "Q"
| R -> "R"
| S -> "S"
| T -> "T"
| U -> "U"
| V -> "V"
| W -> "W"
| X -> "X"
| Y -> "Y"
| Z -> "Z"
| LeftBracket -> "LeftBracket"
| Backslash -> "Backslash"
| RightBracket -> "RightBracket"
| GraveAccent -> "GraveAccent"
| World1 -> "World1"
| World2 -> "World2"
| Escape -> "Escape"
| Enter -> "Enter"
| Tab -> "Tab"
| Backspace -> "Backspace"
| Insert -> "Insert"
| Delete -> "Delete"
| Right -> "Right"
| Left -> "Left"
| Down -> "Down"
| Up -> "Up"
| PageUp -> "PageUp"
| PageDown -> "PageDown"
| Home -> "Home"
| End -> "End"
| CapsLock -> "CapsLock"
| ScrollLock -> "ScrollLock"
| NumLock -> "NumLock"
| PrintScreen -> "PrintScreen"
| Pause -> "Pause"
| F1 -> "F1"
| F2 -> "F2"
| F3 -> "F3"
| F4 -> "F4"
| F5 -> "F5"
| F6 -> "F6"
| F7 -> "F7"
| F8 -> "F8"
| F9 -> "F9"
| F10 -> "F10"
| F11 -> "F11"
| F12 -> "F12"
| F13 -> "F13"
| F14 -> "F14"
| F15 -> "F15"
| F16 -> "F16"
| F17 -> "F17"
| F18 -> "F18"
| F19 -> "F19"
| F20 -> "F20"
| F21 -> "F21"
| F22 -> "F22"
| F23 -> "F23"
| F24 -> "F24"
| F25 -> "F25"
| Kp0 -> "Kp0"
| Kp1 -> "Kp1"
| Kp2 -> "Kp2"
| Kp3 -> "Kp3"
| Kp4 -> "Kp4"
| Kp5 -> "Kp5"
| Kp6 -> "Kp6"
| Kp7 -> "Kp7"
| Kp8 -> "Kp8"
| Kp9 -> "Kp9"
| KpDecimal -> "KpDecimal"
| KpDivide -> "KpDivide"
| KpMultiply -> "KpMultiply"
| KpSubtract -> "KpSubtract"
| KpAdd -> "KpAdd"
| KpEnter -> "KpEnter"
| KpEqual -> "KpEqual"
| LeftShift -> "LeftShift"
| LeftControl -> "LeftControl"
| LeftAlt -> "LeftAlt"
| LeftSuper -> "LeftSuper"
| RightShift -> "RightShift"
| RightControl -> "RightControl"
| RightAlt -> "RightAlt"
| RightSuper -> "RightSuper"
| Menu -> "Menu")
let pp_glfw_key_action : GLFW.key_action F.t =
fun ppf s ->
F.pf ppf
GLFW.(
match s with
| Release -> "Release"
| Press -> "Press"
| Repeat -> "Repeat")
let pp_glfw_mods =
F.(
list (fun ppf s ->
pf ppf
GLFW.(
match s with
| Shift -> "Shift"
| Control -> "Control"
| Alt -> "Alt"
| Super -> "Super")))
let () = let () =
GLFW.init (); GLFW.init ();
at_exit GLFW.terminate; at_exit GLFW.terminate;
@ -218,24 +70,10 @@ let () =
F.pr "oplevel.ml: Toploop.initialize_toplevel_env@."; F.pr "oplevel.ml: Toploop.initialize_toplevel_env@.";
Toploop.initialize_toplevel_env (); Toploop.initialize_toplevel_env ();
let text = let rootrepo =
Lwt_main.run Lwt_main.run
((fun () -> (Store.init_default
Store.init_default (F.str "%s/console/rootstore.git" Secrets.giturl))
(F.str "%s/console/rootstore.git" Secrets.giturl)
>>= fun t ->
Store.S.tree t >>= fun rootstore ->
(try Store.S.Tree.get rootstore [ ".config"; "init.ml" ] with
| Not_found | Invalid_argument _ ->
Lwt.return
"print_newline \"rootstore://.config/init.ml not \
found\";;"
| exc ->
Lwt.return
(F.str ".config/init.ml load exception: %s"
(Printexc.to_string exc)))
>>= fun text -> Lwt.return text)
())
in in
let ui = let ui =
@ -245,17 +83,32 @@ let () =
GLFW.setKeyCallback ~window GLFW.setKeyCallback ~window
~f: ~f:
(Some (Some
(fun window key int state mods -> Glfw_types.(
F.epr fun _window key int state mods ->
"GLFW.setKeyCallback ~f: _win key=%a int=%d state=%a \ F.epr
mods=%a@." "GLFW.setKeyCallback ~f: _win key=%a int=%d state=%a \
pp_glfw_key key int pp_glfw_key_action state pp_glfw_mods mods=%a@."
mods; pp_key key int pp_key_action state pp_mods mods;
Ogui.Ui.keycallback ui window key int state mods)) Ogui.Ui.keycallback ui state key mods))
|> ignore; |> ignore;
let t = GLFW.getTime () |> ref in GLFW.setCharCallback ~window
~f:
(Some
(fun _window ch ->
let uc = Uchar.of_int ch in
F.epr "GLFW.setCharCallback ~f: _win ch=%d(%a)@." ch
F.(option string)
(if Uchar.is_char uc then
Some (String.make 1 @@ Uchar.to_char uc)
else None);
Ogui.Ui.chrcallback ui ch))
|> ignore;
F.pr "oplevel.ml: entering drawing loop@.";
let t = GLFW.getTime () |> ref in
while (not GLFW.(windowShouldClose ~window)) && !continue do while (not GLFW.(windowShouldClose ~window)) && !continue do
Lwt_main.run Lwt_main.run
((fun () -> ((fun () ->
@ -277,24 +130,27 @@ let () =
Gl.clear Gl.clear
(Gl.color_buffer_bit lor Gl.depth_buffer_bit (Gl.color_buffer_bit lor Gl.depth_buffer_bit
lor Gl.stencil_buffer_bit); lor Gl.stencil_buffer_bit);
Gl.enable Gl.blend; Gl.enable Gl.blend;
Gl.blend_func Gl.src_alpha Gl.one_minus_src_alpha; Gl.blend_func Gl.src_alpha Gl.one_minus_src_alpha;
Gl.enable Gl.cull_face_enum; Gl.enable Gl.cull_face_enum;
Gl.disable Gl.depth_test; Gl.disable Gl.depth_test;
let win_w, win_h = (float win_w, float win_h) in let width, height = (float win_w, float win_h) in
Gv.begin_frame ctx ~width:win_w ~height:win_h
~device_ratio:1.;
Perfgraph.render graph ctx (win_w -. 205.) 5.; Gv.begin_frame ctx ~width ~height ~device_ratio:1.;
Perfgraph.render graph ctx (width -. 205.) 5.;
ignore Ogui.TextEdit.(show (multiline (String text)) ui); ignore
Ogui.TextEdit.(
show
(multiline
(Ogui.TextBuffer.of_repo
~path:[ ".config"; "init.ml" ]
~repo:rootrepo))
ui);
(* Demo.render_demo ctx mx my win_w win_h now !blowup data; *) (* Demo.render_demo ctx mx my win_w win_h now !blowup data; *)
Gv.end_frame ctx; Gv.end_frame ctx;
Gc.major_slice 0 |> ignore; Gc.major_slice 0 |> ignore;
GLFW.swapBuffers ~window; GLFW.swapBuffers ~window;
GLFW.pollEvents (); GLFW.pollEvents ();
Lwt.return_unit) Lwt.return_unit)