events
This commit is contained in:
564
ogui.ml
564
ogui.ml
@ -1,3 +1,4 @@
|
||||
open Lwt.Infix
|
||||
module Gv = Graphv_gles2_native
|
||||
module F = Fmt
|
||||
|
||||
@ -26,6 +27,15 @@ end
|
||||
|
||||
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
|
||||
type t = {
|
||||
click : bool;
|
||||
@ -41,25 +51,195 @@ module Sense = struct
|
||||
{ click = false; drag = false; focusable = true; edit = false }
|
||||
end
|
||||
|
||||
module EventFilter = struct
|
||||
type t = {
|
||||
tab : bool;
|
||||
horizontal_arrrows : bool;
|
||||
vertical_arrows : bool;
|
||||
escape : bool;
|
||||
}
|
||||
module TextBuffer = struct
|
||||
type t =
|
||||
| Tree of {
|
||||
mutable path : string list;
|
||||
mutable tree : Store.S.tree;
|
||||
repo : Store.Sync.db;
|
||||
}
|
||||
| Buffer of { name : string; buf : Buffer.t }
|
||||
|
||||
let default =
|
||||
{
|
||||
tab = false;
|
||||
horizontal_arrrows = false;
|
||||
vertical_arrows = false;
|
||||
escape = false;
|
||||
}
|
||||
let of_repo ~path ~(repo : Store.Sync.db) =
|
||||
let tree = Lwt_main.run ((fun () -> Store.S.tree repo) ()) in
|
||||
Tree { path; tree; repo }
|
||||
|
||||
let buffer ~name ~buf = Buffer { name; buf }
|
||||
|
||||
let insert_uchar t n uc : t Lwt.t =
|
||||
F.epr "TextBuffer.insert_uchar %d %s" n (string_of_utf_8_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
|
||||
|
||||
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 rec 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 event_filter = EventFilter.t
|
||||
|
||||
module Context = struct
|
||||
type t = { derp : bool }
|
||||
@ -250,7 +430,7 @@ module TextLayout = struct
|
||||
}
|
||||
|
||||
type layout_job = {
|
||||
text : string;
|
||||
text : TextBuffer.t;
|
||||
sections : layout_section array;
|
||||
wrap : text_wrapping;
|
||||
first_row_min_height : float;
|
||||
@ -264,7 +444,9 @@ module TextLayout = struct
|
||||
F.(
|
||||
record
|
||||
[
|
||||
field "text" (fun s -> String.length s.text) int;
|
||||
field "text"
|
||||
(fun s -> Lwt_main.run (TextBuffer.length s.text))
|
||||
int;
|
||||
field "sections"
|
||||
(fun s -> s.sections)
|
||||
(array pp_layout_section);
|
||||
@ -279,7 +461,9 @@ module TextLayout = struct
|
||||
|
||||
let default_layout_job () =
|
||||
{
|
||||
text = "";
|
||||
text =
|
||||
TextBuffer.buffer ~name:"default_layout_job"
|
||||
~buf:(Buffer.create 32);
|
||||
sections = Array.make 0 layout_section_default;
|
||||
wrap = default_text_wrapping ();
|
||||
first_row_min_height = 0.0;
|
||||
@ -461,6 +645,7 @@ module TextLayout = struct
|
||||
match c with
|
||||
| Some (a, b) ->
|
||||
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))
|
||||
| None -> None
|
||||
|
||||
@ -473,7 +658,7 @@ module TextLayout = struct
|
||||
Array.make 1
|
||||
{
|
||||
leading_space = 0.0;
|
||||
byte_range = (0, String.length text);
|
||||
byte_range = (0, Lwt_main.run (TextBuffer.length text));
|
||||
format;
|
||||
};
|
||||
wrap =
|
||||
@ -481,7 +666,8 @@ module TextLayout = struct
|
||||
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
|
||||
text;
|
||||
@ -489,7 +675,7 @@ module TextLayout = struct
|
||||
Array.make 1
|
||||
{
|
||||
leading_space = 0.0;
|
||||
byte_range = (0, String.length text);
|
||||
byte_range = (0, Lwt_main.run (TextBuffer.length text));
|
||||
format;
|
||||
};
|
||||
wrap =
|
||||
@ -497,7 +683,8 @@ module TextLayout = struct
|
||||
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
|
||||
text;
|
||||
@ -505,7 +692,7 @@ module TextLayout = struct
|
||||
Array.make 1
|
||||
{
|
||||
leading_space = 0.0;
|
||||
byte_range = (0, String.length text);
|
||||
byte_range = (0, Lwt_main.run (TextBuffer.length text));
|
||||
format;
|
||||
};
|
||||
wrap = default_text_wrapping ();
|
||||
@ -585,7 +772,8 @@ module TextLayout = struct
|
||||
let lines = Gv.Text.make_empty_rows job.wrap.max_rows in
|
||||
let row_count =
|
||||
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
|
||||
(* F.epr "row_count=%d@." row_count; *)
|
||||
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;*)
|
||||
Array.iter
|
||||
Gv.(
|
||||
fun (row : TextLayout.row) ->
|
||||
let sections =
|
||||
List.filter
|
||||
(fun (r : TextLayout.layout_section) ->
|
||||
fst r.byte_range <= row.text_row.end_index
|
||||
&& snd r.byte_range > row.text_row.start_index)
|
||||
(Array.to_list
|
||||
@@ Array.sub g.job.sections row.section_index_at_start
|
||||
(Array.length g.job.sections
|
||||
- row.section_index_at_start))
|
||||
in
|
||||
assert (List.length sections > 0);
|
||||
(fun (row : TextLayout.row) ->
|
||||
let sections =
|
||||
List.filter
|
||||
(fun (r : TextLayout.layout_section) ->
|
||||
fst r.byte_range <= row.text_row.end_index
|
||||
&& snd r.byte_range > row.text_row.start_index)
|
||||
(Array.to_list
|
||||
@@ Array.sub g.job.sections row.section_index_at_start
|
||||
(Array.length g.job.sections
|
||||
- row.section_index_at_start))
|
||||
in
|
||||
assert (List.length sections > 0);
|
||||
|
||||
ignore
|
||||
Gg.(
|
||||
List.fold_left
|
||||
(fun x (sec : TextLayout.layout_section) ->
|
||||
let start, end_ =
|
||||
( min
|
||||
(String.length g.job.text - 1)
|
||||
(max 0
|
||||
(max (fst sec.byte_range)
|
||||
row.text_row.start_index)),
|
||||
min
|
||||
(String.length g.job.text - 1)
|
||||
(max 0
|
||||
(min (snd sec.byte_range)
|
||||
row.text_row.end_index)) )
|
||||
in
|
||||
ignore
|
||||
Gg.(
|
||||
List.fold_left
|
||||
(fun x (sec : TextLayout.layout_section) ->
|
||||
let start, end_ =
|
||||
( min
|
||||
(Lwt_main.run (TextBuffer.length g.job.text) - 1)
|
||||
(max 0
|
||||
(max (fst sec.byte_range)
|
||||
row.text_row.start_index)),
|
||||
min
|
||||
(Lwt_main.run (TextBuffer.length g.job.text) - 1)
|
||||
(max 0
|
||||
(min (snd sec.byte_range)
|
||||
row.text_row.end_index)) )
|
||||
in
|
||||
|
||||
let font_name, font_size =
|
||||
match sec.format.font_id with
|
||||
| Default -> ("mono", 18.)
|
||||
| FontId (s, size) -> (s, size)
|
||||
in
|
||||
Text.set_font_face t ~name:font_name;
|
||||
Text.set_size t ~size:font_size;
|
||||
Text.set_align t ~align:Align.(left lor top);
|
||||
let metrics = Gv.Text.metrics t in
|
||||
let bounds =
|
||||
Gv.Text.bounds t ~x ~y:0. ~start ~end_ g.job.text
|
||||
in
|
||||
Path.begin_ t;
|
||||
Path.rect t ~x ~y:(Box2.miny row.rect)
|
||||
~w:bounds.advance ~h:metrics.line_height;
|
||||
set_fill_color t ~color:sec.format.background;
|
||||
fill t;
|
||||
let font_name, font_size =
|
||||
match sec.format.font_id with
|
||||
| Default -> ("mono", 18.)
|
||||
| FontId (s, size) -> (s, size)
|
||||
in
|
||||
let open Gv in
|
||||
Text.set_font_face t ~name:font_name;
|
||||
Text.set_size t ~size:font_size;
|
||||
Text.set_align t ~align:Align.(left lor top);
|
||||
let metrics = Gv.Text.metrics t in
|
||||
let bounds =
|
||||
Gv.Text.bounds t ~x ~y:0. ~start ~end_
|
||||
(Lwt_main.run (TextBuffer.contents g.job.text))
|
||||
in
|
||||
Path.begin_ t;
|
||||
Path.rect t ~x ~y:(Box2.miny row.rect)
|
||||
~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;
|
||||
let w =
|
||||
Text.text_w t ~x ~y:(Box2.miny row.rect) ~start
|
||||
~end_ g.job.text
|
||||
in
|
||||
w)
|
||||
(Box2.minx row.rect) sections))
|
||||
set_fill_color t ~color:sec.format.color;
|
||||
let w =
|
||||
Text.text_w t ~x ~y:(Box2.miny row.rect) ~start
|
||||
~end_
|
||||
(Lwt_main.run (TextBuffer.contents g.job.text))
|
||||
in
|
||||
w)
|
||||
(Box2.minx row.rect) sections))
|
||||
g.rows
|
||||
end
|
||||
|
||||
@ -877,25 +1067,18 @@ module Placer = struct
|
||||
end
|
||||
|
||||
module Ui = struct
|
||||
type key_callback =
|
||||
t ->
|
||||
GLFW.window ->
|
||||
GLFW.key ->
|
||||
int ->
|
||||
GLFW.key_action ->
|
||||
GLFW.key_mod list ->
|
||||
unit
|
||||
|
||||
and t = {
|
||||
type t = {
|
||||
id : id;
|
||||
style : Style.t;
|
||||
placer : Placer.t;
|
||||
enabled : bool;
|
||||
gv : Gv.t;
|
||||
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 spacing ui = ui.style.spacing
|
||||
let available_size ui = Placer.available_size ui.placer
|
||||
@ -907,7 +1090,6 @@ module Ui = struct
|
||||
(!id, size)
|
||||
|
||||
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 id, rect = allocate_space gv rect in
|
||||
@ -920,56 +1102,53 @@ module Ui = struct
|
||||
enabled = true;
|
||||
gv;
|
||||
glfw_window = window;
|
||||
key = key_callback_default;
|
||||
bindings = Event.empty;
|
||||
}
|
||||
|
||||
let keycallback t window key int state mods : unit =
|
||||
t.key t window key int state mods
|
||||
let callback_resolver : action list Event.resolver option ref =
|
||||
ref Option.None
|
||||
|
||||
let add_key_callback (t : t) ~(f : key_callback) : unit =
|
||||
let g = t.key in
|
||||
t.key <-
|
||||
(fun a1 a2 a3 a4 a5 a6 ->
|
||||
f a1 a2 a3 a4 a5 a6;
|
||||
g a1 a2 a3 a4 a5 a6)
|
||||
let keycallback t key state mods : unit =
|
||||
let res =
|
||||
match !callback_resolver with
|
||||
| Some res -> res
|
||||
| None -> Event.resolver [ Event.pack Fun.id t.bindings ]
|
||||
in
|
||||
|
||||
let interact (_ui : t) rect (id : id option) sense : Response.t =
|
||||
let id = Option.value id ~default:(-1) in
|
||||
{
|
||||
ctx = { derp = false };
|
||||
id;
|
||||
rect;
|
||||
interact_rect = rect;
|
||||
sense;
|
||||
enabled = true;
|
||||
contains_pointer = (*TODO*) true;
|
||||
hovered = (*TODO*) true;
|
||||
highlighted = (*TODO*) true;
|
||||
clicked = (*TODO*) true;
|
||||
fake_primary_click = (*TODO*) false;
|
||||
long_touched = (*TODO*) false;
|
||||
drag_started = false;
|
||||
dragged = false;
|
||||
drag_stopped = false;
|
||||
is_pointer_button_down_on = false;
|
||||
interact_pointer_pos = None;
|
||||
changed = false;
|
||||
}
|
||||
end
|
||||
ignore
|
||||
@@ Lwt_main.run
|
||||
((fun () : bool Lwt.t ->
|
||||
match Event.resolve (Key (key, state, mods)) res with
|
||||
| Event.Accepted actions ->
|
||||
callback_resolver := None;
|
||||
let rec exec : action list -> bool Lwt.t = function
|
||||
| Custom f :: actions ->
|
||||
f () >>= fun () -> exec actions
|
||||
| [] -> Lwt.return false
|
||||
in
|
||||
exec actions
|
||||
| Event.Continue res ->
|
||||
callback_resolver := Some res;
|
||||
Lwt.return true
|
||||
| Event.Rejected ->
|
||||
callback_resolver := None;
|
||||
Lwt.return false)
|
||||
())
|
||||
|
||||
module TextBuffer = struct
|
||||
type t = String of string
|
||||
let chrcallback_ref : (Uchar.t -> unit Lwt.t) ref =
|
||||
ref (fun c ->
|
||||
F.epr "chrcallback: '%a'@." pp_uchar c;
|
||||
Lwt.return_unit)
|
||||
|
||||
let is_mutable = function String _ -> true
|
||||
let as_string = function String s -> s
|
||||
let chrcallback _t (chr : int) : unit =
|
||||
Lwt_main.run @@ !chrcallback_ref @@ Uchar.of_int chr
|
||||
end
|
||||
|
||||
module TextEdit = struct
|
||||
open Gg
|
||||
|
||||
type t = {
|
||||
text : TextBuffer.t;
|
||||
hint_text : TextLayout.widget_text;
|
||||
mutable text : TextBuffer.t;
|
||||
id : id option;
|
||||
id_source : id option;
|
||||
text_format : TextLayout.text_format;
|
||||
@ -982,7 +1161,6 @@ module TextEdit = struct
|
||||
interactive : bool;
|
||||
desired_width : float option;
|
||||
desired_height_rows : int;
|
||||
event_filter : event_filter;
|
||||
cursor_at_end : bool;
|
||||
min_size : Gg.v2;
|
||||
align : align;
|
||||
@ -990,7 +1168,7 @@ module TextEdit = struct
|
||||
char_limit : int; (* return_key : keyboard_shortcut; *)
|
||||
}
|
||||
|
||||
type state = {
|
||||
and state = {
|
||||
mutable cursor : TextLayout.cursor_state;
|
||||
(* undoer : undoer; *)
|
||||
singleline_offset : float;
|
||||
@ -999,27 +1177,80 @@ module TextEdit = struct
|
||||
|
||||
let state_mem : (int * state) list ref = ref []
|
||||
|
||||
let process_key (state : state) (key : GLFW.key)
|
||||
(action : GLFW.key_action) (mods : GLFW.key_mod list) : unit =
|
||||
let add_bindings (t : t) (ui : Ui.t) (state : state) : unit Lwt.t =
|
||||
let open GLFW in
|
||||
match (action, key, mods) with
|
||||
| Press, F, [ Control ] | Press, Right, [] ->
|
||||
state.cursor <-
|
||||
TextLayout.cursor_state_update
|
||||
~f:(fun a b ->
|
||||
F.epr "cursor_state_update %d %d@." a b;
|
||||
(a + 1, b + 1))
|
||||
state.cursor
|
||||
| Press, B, [ Control ] | Press, Left, [] ->
|
||||
state.cursor <-
|
||||
TextLayout.cursor_state_update
|
||||
~f:(fun a b ->
|
||||
F.epr "cursor_state_update %d %d@." a b;
|
||||
(a - 1, b - 1))
|
||||
state.cursor
|
||||
| _ -> ()
|
||||
let open Event in
|
||||
let open Ui in
|
||||
ui.bindings <-
|
||||
empty
|
||||
|> adds
|
||||
[
|
||||
[ Key (Press, F, [ Control ]) ];
|
||||
[ Key (Press, Right, []) ];
|
||||
]
|
||||
[
|
||||
Custom
|
||||
(fun () ->
|
||||
state.cursor <-
|
||||
TextLayout.cursor_state_update
|
||||
~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)
|
||||
>>= 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
|
||||
| Some state -> state
|
||||
| None ->
|
||||
@ -1031,14 +1262,10 @@ module TextEdit = struct
|
||||
}
|
||||
in
|
||||
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
|
||||
|
||||
type output = {
|
||||
response : Response.t;
|
||||
(* response : Response.t; *)
|
||||
galley : TextLayout.galley;
|
||||
galley_pos : Gg.p2;
|
||||
text_clip_rect : Gg.box2;
|
||||
@ -1050,7 +1277,6 @@ module TextEdit = struct
|
||||
(text : TextBuffer.t) : t =
|
||||
{
|
||||
text;
|
||||
hint_text = RichText TextLayout.rich_text_default;
|
||||
id = None;
|
||||
id_source = None;
|
||||
text_format;
|
||||
@ -1062,13 +1288,6 @@ module TextEdit = struct
|
||||
interactive = true;
|
||||
desired_width = None;
|
||||
desired_height_rows = 4;
|
||||
event_filter =
|
||||
{
|
||||
EventFilter.default with
|
||||
horizontal_arrrows = true;
|
||||
vertical_arrows = true;
|
||||
tab = false;
|
||||
};
|
||||
cursor_at_end = true;
|
||||
min_size = Gg.V2.zero;
|
||||
align = Min;
|
||||
@ -1078,6 +1297,7 @@ module TextEdit = struct
|
||||
}
|
||||
|
||||
let show_content (t : t) (ui : Ui.t) : output =
|
||||
let state = load_state (Option.value ~default:(-1) t.id) in
|
||||
let origin = Ui.cursor_origin ui in
|
||||
(* TODO .or(ui.visuals().override_text_color) *)
|
||||
(* let row_height = (Gv.Text.metrics ui.gv).line_height in *)
|
||||
@ -1093,23 +1313,17 @@ module TextEdit = struct
|
||||
available_width
|
||||
else Float.min desired_width available_width
|
||||
in
|
||||
let state = load_state ui (Option.value ~default:(-1) t.id) in
|
||||
let default_layouter (ui : Ui.t) (text : TextBuffer.t)
|
||||
(wrap_width : size1) : TextLayout.galley =
|
||||
let font =
|
||||
match Gv.Text.find_font ui.gv ~name:"mono" with
|
||||
| Some gv -> Fonts.{ gv; pixels_per_point = 1.0 }
|
||||
| None -> failwith "can't font font 'mono'"
|
||||
| None -> failwith "can't find font 'mono'"
|
||||
in
|
||||
let layout_job =
|
||||
if t.multiline then
|
||||
TextLayout.simple
|
||||
(TextBuffer.as_string text)
|
||||
t.text_format wrap_width
|
||||
else
|
||||
TextLayout.simple_singleline
|
||||
(TextBuffer.as_string text)
|
||||
t.text_format
|
||||
TextLayout.simple text t.text_format wrap_width
|
||||
else TextLayout.simple_singleline text t.text_format
|
||||
in
|
||||
let layout_job =
|
||||
TextLayout.with_cursor
|
||||
@ -1145,9 +1359,9 @@ module TextEdit = struct
|
||||
(* TODO state = TextEditState::load(ui.ctx(), id)... *)
|
||||
(* TODO moved up let state = load_state (Option.value ~default:(-1) t.id) in *)
|
||||
(* 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 *)
|
||||
let text_clip_rect = rect in
|
||||
(* let painter = Ui.painter_at ui text_clip_rect in *)
|
||||
@ -1162,7 +1376,6 @@ module TextEdit = struct
|
||||
|
||||
let _align_offset = rect in
|
||||
{
|
||||
response;
|
||||
galley;
|
||||
galley_pos = Box2.o galley_pos;
|
||||
text_clip_rect;
|
||||
@ -1171,11 +1384,8 @@ module TextEdit = struct
|
||||
}
|
||||
|
||||
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 output = show_content t ui in
|
||||
let _outer_rect = output.response.rect in
|
||||
(* let _outer_rect = output.response.rect in *)
|
||||
output
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user