use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use crate::consts::*;
use crate::keys::*;
use crate::value::{fmt_f64_sep, parse_f64_sep, rf_fix, rf_scaled, RangeF};
use crate::{Sp, Ui};
use text::segment;
#[path = "text_surface.rs"]
mod surface;
pub use surface::TextSurfaceLine;
#[path = "edit_spell.rs"]
mod spellbox;
const TB_NAT_W: i32 = 200; const NB_NAT_W: i32 = 120; const TB_FLAG_MASK: u32 = 0xFF80_0000;
const ML_MAX: usize = 4096; const PW_MAX: usize = 1024; const UD_N: usize = 16; const UD_SEG: usize = 2048; const TXQ_MAX: usize = super::ctx::TEXTQ_MAX; const NB_CAP: u32 = 32;
#[derive(Clone, Copy)]
pub struct TextboxPresentation {
pub font: u32,
pub size: i32,
pub line_height: i32,
pub baseline: i32,
pub color: u32,
pub background: u32,
pub underline: bool,
}
const CTRL_KEYS: [u32; 13] = [65, 67, 88, 86, 90, 89, VK_LEFT, VK_RIGHT, VK_HOME, VK_END, VK_BACK, VK_DELETE, VK_INSERT];
pub(crate) struct S {
pub(crate) hclip: Option<Vec<u8>>,
pub(crate) hclip_fmt: u32,
pub(crate) hclip_rich: Vec<(u32, Vec<u8>)>,
buf: Vec<u8>, disp: Vec<u8>, tb_id: u32,
tb_max: usize, tb_flags: u32,
tb_chg: bool,
tb_ev: u32,
tb_natw: i32,
tb_base: i32,
tb_font: u32,
tb_size: i32,
tb_sem: Option<usize>, presentation: Option<TextboxPresentation>,
surface: Option<Vec<TextSurfaceLine>>,
surface_start: Option<usize>,
surface_change: Option<(usize, usize, String)>,
ime_frame: Option<u32>, ed_id: u32, ed_blur: u32, ed_caret: usize,
ed_anchor: usize,
ed_t0: u32, ed_merge: bool, ed_drag: bool, ed_seen: u32, ed_self: bool, ed_multi: bool, ed_prefx: i32, ud_base: usize,
ud_pos: usize,
ud_count: usize,
ud_hdr: [[usize; 3]; UD_N], ud_data: [Vec<u8>; UD_N],
nb_id: u32, nb_seen: u32,
nb_pend: u32,
nb_pend_ev: u32,
nb_kind: u32, nb_rg: Range, nb_rf: RangeF,
nb_shown: u64, nb_buf: Vec<u8>,
nb_view: Vec<u8>,
nb_orig: Vec<u8>, pend_text: Option<(u32, u32, Vec<u8>)>, ml_start: Vec<usize>, ml_hyph: Vec<bool>, hyph: Option<alloc::sync::Arc<spell::Hyphenator>>, ml_w: i32, ml_lh: i32, ml_page: i32, spell: spellbox::S,
}
impl Default for S {
fn default() -> S {
S {
hclip: None,
hclip_fmt: 0,
hclip_rich: Vec::new(),
buf: Vec::new(),
disp: Vec::new(),
tb_id: 0,
tb_max: 0,
tb_flags: 0,
tb_chg: false,
tb_ev: 0,
tb_natw: 0,
tb_base: 0,
tb_font: 0,
tb_size: 0,
tb_sem: None,
presentation: None,
surface: None,
surface_start: None,
surface_change: None,
ime_frame: None,
ed_id: 0,
ed_blur: 0,
ed_caret: 0,
ed_anchor: 0,
ed_t0: 0,
ed_merge: false,
ed_drag: false,
ed_seen: 0,
ed_self: false,
ed_multi: false,
ed_prefx: -1,
ud_base: 0,
ud_pos: 0,
ud_count: 0,
ud_hdr: [[0; 3]; UD_N],
ud_data: core::array::from_fn(|_| Vec::new()),
nb_id: 0,
nb_seen: 0,
nb_pend: 0,
nb_pend_ev: 0,
nb_kind: 0,
nb_rg: Range::default(),
nb_rf: RangeF::default(),
nb_shown: 0,
nb_buf: Vec::new(),
nb_view: Vec::new(),
nb_orig: Vec::new(),
pend_text: None,
ml_start: Vec::new(),
ml_hyph: Vec::new(),
hyph: None,
ml_w: 0,
ml_lh: 0,
ml_page: 0,
spell: Default::default(),
}
}
}
fn disp_of(e: &S) -> &[u8] {
if e.tb_flags & TB_PASSWORD != 0 { &e.disp } else { &e.buf }
}
fn at(s: &[u8], i: usize) -> u8 {
if i < s.len() { s[i] } else { 0 }
}
fn u8_decode(s: &[u8], i: usize) -> (u32, usize) {
let b0 = at(s, i) as u32;
if b0 < 0x80 {
return (b0, i + 1);
}
let (mut cp, n, min) = if b0 < 0xC2 {
return (0xFFFD, i + 1);
} else if b0 < 0xE0 {
(b0 & 0x1F, 1, 0x80)
} else if b0 < 0xF0 {
(b0 & 0x0F, 2, 0x800)
} else if b0 < 0xF5 {
(b0 & 7, 3, 0x10000)
} else {
return (0xFFFD, i + 1);
};
let mut j = i + 1;
for _ in 0..n {
let c = at(s, j) as u32;
if c & 0xC0 != 0x80 {
return (0xFFFD, i + 1);
}
cp = cp << 6 | (c & 0x3F);
j += 1;
}
if cp < min || cp > 0x10FFFF || cp & 0xFFFF_F800 == 0xD800 {
return (0xFFFD, i + 1);
}
(cp, j)
}
fn u8_next(s: &[u8], i: usize) -> usize {
if at(s, i) == 0 {
return i;
}
u8_decode(s, i).1
}
fn u8_prev(s: &[u8], i: usize) -> usize {
let mut p = i;
if p == 0 {
return 0;
}
loop {
p -= 1;
if p == 0 {
return 0;
}
if s[p] & 0xC0 != 0x80 {
return p;
}
}
}
fn u8_clip(s: &[u8], n: usize) -> usize {
let mut k = n;
while k != 0 && at(s, k) & 0xC0 == 0x80 {
k -= 1;
}
k
}
fn u8_push(out: &mut Vec<u8>, cp: u32) {
let c = if cp > 0x10FFFF || cp & 0xFFFF_F800 == 0xD800 { 0xFFFD } else { cp };
if c < 0x80 {
out.push(c as u8);
} else if c < 0x800 {
out.push(0xC0 | (c >> 6) as u8);
out.push(0x80 | (c & 0x3F) as u8);
} else if c < 0x10000 {
out.push(0xE0 | (c >> 12) as u8);
out.push(0x80 | ((c >> 6) & 0x3F) as u8);
out.push(0x80 | (c & 0x3F) as u8);
} else {
out.push(0xF0 | (c >> 18) as u8);
out.push(0x80 | ((c >> 12) & 0x3F) as u8);
out.push(0x80 | ((c >> 6) & 0x3F) as u8);
out.push(0x80 | (c & 0x3F) as u8);
}
}
fn to_string(mut b: Vec<u8>) -> String {
if let Some(p) = b.iter().position(|&c| c == 0) {
b.truncate(p);
}
match String::from_utf8(b) {
Ok(s) => s,
Err(e) => String::from_utf8_lossy(e.as_bytes()).into_owned(),
}
}
fn tb_owns(entry: u32, multi: bool) -> bool {
let vk = entry & 0xFF;
let m = entry >> 16;
if vk == VK_TAB {
return false;
}
if multi {
if vk == VK_UP || vk == VK_DOWN || vk.wrapping_sub(VK_PRIOR) <= VK_NEXT - VK_PRIOR {
return m & (MOD_ALT | MOD_CTRL) == 0;
}
if vk == VK_RETURN {
return m & MOD_ALT == 0;
}
}
if vk == VK_UP || vk == VK_DOWN || vk.wrapping_sub(VK_PRIOR) <= VK_NEXT - VK_PRIOR {
return false;
}
if vk.wrapping_sub(VK_SHIFT) <= VK_CAPITAL - VK_SHIFT || vk.wrapping_sub(VK_LWIN) <= VK_APPS - VK_LWIN || vk.wrapping_sub(VK_F1) <= 23 || vk.wrapping_sub(VK_NUMLOCK) <= 1
|| vk.wrapping_sub(VK_LSHIFT) <= VK_RMENU - VK_LSHIFT
{
return false;
}
if m & MOD_ALTGR != 0 || m & (MOD_CTRL | MOD_ALT) == MOD_CTRL | MOD_ALT {
return true;
}
if m & MOD_ALT != 0 {
return false;
}
if m & MOD_CTRL == 0 {
return true;
}
CTRL_KEYS.contains(&vk)
}
fn tb_accept(cp: u32, flags: u32) -> bool {
if cp < 32 || cp == 127 {
return false;
}
if flags & TB_DIGITS == 0 || cp.wrapping_sub(48) <= 9 {
return true;
}
if flags & TB_DECIMAL != 0 && (cp == 44 || cp == 46) {
return true; }
cp == 45 && flags & TB_SIGNED != 0
}
fn nb_format(v: i32) -> Vec<u8> {
format!("{}", v).into_bytes()
}
fn nb_parse_text(t: &[u8], stored: i32) -> i64 {
let mut i = 0;
let neg = at(t, 0) == b'-';
if neg {
i = 1;
}
let mut v: i64 = 0;
let mut digits = 0;
while i < NB_CAP as usize {
let d = at(t, i).wrapping_sub(48);
if d > 9 {
break;
}
digits += 1;
v = v * 10 + d as i64;
if v as u64 > 0x8000_0000 {
v = 0x8000_0000;
}
i += 1;
}
if digits == 0 {
return stored as i64;
}
if neg { -v } else { v }
}
fn rg_clamp(v: i64, rg: &Range) -> i32 {
let mut c = v;
if c < rg.min as i64 {
c = rg.min as i64;
}
if c > rg.max as i64 {
c = rg.max as i64;
}
c as i32
}
impl Ui {
pub fn textbox_selection(&self, id: u32) -> Option<(usize, usize)> {
if self.m.edit.ed_id == id && self.focused() == id {
Some((self.m.edit.ed_anchor, self.m.edit.ed_caret))
} else { None }
}
pub fn textbox_select(&mut self, id: u32, text: &str, anchor: usize, caret: usize) -> bool {
if self.m.edit.ed_id != id || self.focused() != id || !text.is_char_boundary(anchor) || !text.is_char_boundary(caret) {
return false;
}
self.m.edit.ed_anchor = anchor;
self.m.edit.ed_caret = caret;
self.m.edit.ed_prefx = -1;
self.tb_blink_reset();
self.repass();
true
}
#[cfg_attr(jfgui_dev, track_caller)]
pub fn textbox(&mut self, id: u32, placeholder: Option<&str>, buf: &mut String, cap_flags: u32) -> u32 {
let _src = self.src_scope();
self.textbox_icon(id, 0, placeholder, buf, cap_flags)
}
#[cfg_attr(jfgui_dev, track_caller)]
pub fn textbox_icon(&mut self, id: u32, icon: u32, placeholder: Option<&str>, buf: &mut String, cap_flags: u32) -> u32 {
let _src = self.src_scope();
self.m.edit.tb_natw = TB_NAT_W;
let mut b = core::mem::take(buf).into_bytes();
let ev = self.tb_core(id, placeholder, &mut b, cap_flags, icon);
*buf = to_string(b);
ev
}
#[cfg_attr(jfgui_dev, track_caller)]
pub fn textbox_presented(&mut self, id: u32, placeholder: Option<&str>, buf: &mut String, cap_flags: u32, mut presentation: TextboxPresentation) -> u32 {
let _src = self.src_scope();
presentation.size = presentation.size.clamp(256, 512 * 256);
presentation.line_height = presentation.line_height.clamp(1, 2048);
presentation.baseline = presentation.baseline.clamp(0, presentation.line_height);
let previous = self.m.edit.presentation.replace(presentation);
let old_font = self.text.get_font();
let ev = self.textbox(id, placeholder, buf, cap_flags | TB_MULTI);
self.m.edit.presentation = previous;
self.text.set_font(old_font.0, old_font.1);
ev
}
fn tb_core(&mut self, id: u32, placeholder: Option<&str>, buf: &mut Vec<u8>, cap_flags: u32, icon: u32) -> u32 {
if let Some(p) = buf.iter().position(|&c| c == 0) {
buf.truncate(p); }
core::mem::swap(buf, &mut self.m.edit.buf);
let set = self.a11y_text(id).map(|t| {
let max = ((cap_flags & TB_CAP_MASK) as usize).saturating_sub(1);
let mut n = if max > 0 { t.len().min(max) } else { t.len() };
while !t.is_char_boundary(n) {
n -= 1;
}
let e = &mut self.m.edit;
e.buf = t.as_bytes()[..n].to_vec();
if e.ed_id == id {
e.ed_caret = n;
e.ed_anchor = n;
}
self.repass();
});
let ph = placeholder.map(|p| p.split('\0').next().unwrap_or(""));
let ev = if cap_flags & TB_MULTI != 0 {
self.tbm_core(id, ph, cap_flags)
} else {
self.tb_core1(id, ph, cap_flags, icon)
};
core::mem::swap(buf, &mut self.m.edit.buf);
if set.is_some() { ev | EV_ACT | EV_COMMIT } else { ev }
}
fn tb_start(&mut self, id: u32, cap_flags: u32, mask: u32) -> u32 {
let e = &mut self.m.edit;
e.tb_id = id;
e.tb_max = ((cap_flags & TB_CAP_MASK) as usize).saturating_sub(1);
e.tb_flags = cap_flags & mask;
e.tb_chg = false;
e.tb_ev = 0;
self.take_flags()
}
fn tb_core1(&mut self, id: u32, placeholder: Option<&str>, cap_flags: u32, icon: u32) -> u32 {
let stflags = self.tb_start(id, cap_flags, TB_FLAG_MASK);
let st = self.style;
let mut sp = Sp { value: self.m.edit.tb_natw, icon, flags: 0, ..Sp::default() };
let (w, h) = st.textbox_metrics(self, &mut sp);
let (w, h) = self.ctrl_size(w, h, stflags, false); if icon != 0 { sp.s[0] += self.dp(23); }
self.place(w, h);
let mut hb = HB_FOCUS | HB_TEXT | CUR_IBEAM << HB_CURSOR_SHIFT;
if stflags & ST_DISABLED != 0 {
hb |= HB_DISABLED;
}
let ev = self.behave(id, hb);
let disabled = self.wp.ctx.last_state == SS_DISABLED as u32;
let mut editing = self.tb_focus();
if editing {
self.m.edit.ed_multi = false; }
let hair = self.hair();
let r = self.wp.ctx.last_rect;
let x0 = sp.s[0].wrapping_add(r[0]);
let tw = r[2].wrapping_sub(sp.s[0].wrapping_add(sp.s[1])).max(1);
let mut scroll = self.slot(id).scrollx;
st.textbox_font(self, &sp);
self.tb_build_disp();
if editing && !self.spell_menu_open(id) {
let x = self.rtl_map(self.wp.ctx.mx, x0, tw).wrapping_sub(x0).wrapping_add(scroll);
self.tb_mouse(ev, x);
self.tb_keys();
self.tb_typed();
#[cfg(target_os = "android")]
self.tb_android_ime();
if self.m.edit.tb_chg {
self.m.edit.tb_ev |= EV_ACT;
self.repass();
self.tb_build_disp();
}
if self.m.edit.ed_id != id {
editing = false; self.ime_off();
}
}
let (lh, base) = st.textbox_font(self, &sp);
let top = (r[3].wrapping_sub(lh) >> 1).wrapping_add(r[1]);
self.m.edit.tb_base = top.wrapping_add(base);
let (font, size) = self.text.get_font();
self.m.edit.tb_font = font;
self.m.edit.tb_size = size;
let last = tw.wrapping_sub(hair); let mut s = scroll;
let mut cx = 0;
if editing {
let d = self.tb_to_disp(self.m.edit.ed_caret);
cx = self.text.caret_x(disp_of(&self.m.edit), d);
if cx.wrapping_sub(s) > last {
s = cx.wrapping_sub(last);
}
if cx < s {
s = cx;
}
}
let dlen = disp_of(&self.m.edit).len();
let ex = self.text.caret_x(disp_of(&self.m.edit), dlen).wrapping_sub(last);
if s > ex {
s = ex;
}
if s < 0 {
s = 0;
}
scroll = s;
self.slot(id).scrollx = s;
sp.place(r);
sp.ev = ev;
sp.value = editing as i32;
sp.value2 = disabled as i32;
st.textbox_frame(self, &sp);
if icon != 0 {
let size = self.dp(16);
self.draw_color(self.color(if disabled { T_TEXT_DISABLED } else { T_TEXT2 }));
self.draw_icon(icon, r[0] + self.dp(8), r[1] + (r[3] - size) / 2, size << 8);
}
self.draw_clip(x0, r[1], tw, r[3]);
self.ltr_begin(x0, tw);
let bx = x0.wrapping_sub(scroll); let tcol = self.color(if disabled { T_TEXT_DISABLED } else { T_TEXT });
if dlen == 0 {
if let Some(p) = placeholder {
let c = self.color(if disabled { T_TEXT_DISABLED } else { T_TEXT2 });
self.tb_emit_hint(x0, p, c, tw);
}
} else {
let (a, b) = self.tb_sel();
if editing && a != b {
let da = self.tb_to_disp(a);
let sx0 = self.text.caret_x(disp_of(&self.m.edit), da);
let db = self.tb_to_disp(b);
let sx1 = self.text.caret_x(disp_of(&self.m.edit), db);
let c = self.color(T_ACCENT);
self.draw_color(c);
self.draw_rect(bx.wrapping_add(sx0), top, sx1.wrapping_sub(sx0), lh);
self.tb_clip_x(r[0], bx.wrapping_add(sx0)); self.tb_emit_disp(bx, tcol);
self.draw_unclip();
self.tb_clip_x(bx.wrapping_add(sx0), bx.wrapping_add(sx1)); let c = self.color(T_ON_ACCENT);
self.tb_emit_disp(bx, c);
self.draw_unclip();
self.tb_clip_x(bx.wrapping_add(sx1), r[0].wrapping_add(r[2])); self.tb_emit_disp(bx, tcol);
self.draw_unclip();
} else {
self.tb_emit_disp(bx, tcol);
}
}
let spell = self.spell_scan(id);
if spell && dlen != 0 {
self.spell_draw(id, bx, 0, dlen, editing);
}
if editing {
let x = bx.wrapping_add(cx);
if self.tb_caret_on() {
let c = self.color(T_TEXT);
self.draw_color(c);
self.draw_rect(x, top, hair, lh);
}
self.ime_pos(x, top, lh);
}
self.ltr_end();
self.draw_unclip();
if spell {
let hit = (ev & EV_RCLICK != 0).then(|| {
self.text.set_font(self.m.edit.tb_font, self.m.edit.tb_size);
let x = self.rtl_map(self.wp.ctx.mx, x0, tw).wrapping_sub(x0).wrapping_add(scroll);
self.tb_hit(x)
});
self.spell_input(id, ev, hit, editing);
}
self.tb_finish(ev, editing)
}
fn tb_finish(&mut self, ev: u32, editing: bool) -> u32 {
#[cfg(target_os = "android")]
if editing {
let e = &self.m.edit;
plat::android_ime_publish(e.tb_id, &e.buf, e.ed_anchor, e.ed_caret, e.tb_flags);
}
if !editing {
self.wp.ctx.last_state &= !(SS_FOCUSED as u32);
}
let (v, mx) = if editing { (self.m.edit.ed_caret as i32, self.m.edit.ed_anchor as i32) } else { (0, 0) };
let pw = self.m.edit.tb_flags & TB_PASSWORD != 0;
let d = core::mem::take(if pw { &mut self.m.edit.disp } else { &mut self.m.edit.buf });
let n0 = self.sems.len();
{
let l = String::from_utf8_lossy(&d);
let id = self.m.edit.tb_id;
let kind = self.m.edit.tb_flags & (TB_MULTI | TB_PASSWORD | TB_READONLY);
let s = self.sem(K_TEXTBOX, id, Some(l.as_ref()), v);
s.max = mx;
s.min = (kind >> 24) as i32; }
self.m.edit.tb_sem = if self.sems.len() > n0 { Some(n0) } else { None };
*(if pw { &mut self.m.edit.disp } else { &mut self.m.edit.buf }) = d;
let mut out = ev & (EV_HOVER | EV_HELD | EV_DOUBLE | EV_RCLICK | EV_DRAG);
if editing {
out |= EV_FOCUS;
}
out | self.m.edit.tb_ev
}
fn tb_sem_set(&mut self, value: i32, max: i32) {
let r = match self.m.edit.tb_sem {
Some(i) if i < self.sems.len() => &mut self.sems[i],
_ => &mut self.sem_dummy,
};
r.value = value;
r.max = max;
}
fn tbm_core(&mut self, id: u32, placeholder: Option<&str>, cap_flags: u32) -> u32 {
let stflags = self.tb_start(id, cap_flags, TB_FLAG_MASK & !TB_PASSWORD);
let hy = cap_flags & (TB_HYPHENATE | TB_WRAP) == TB_HYPHENATE | TB_WRAP;
self.m.edit.hyph = self.hyph_take(hy); let presentation = self.m.edit.presentation;
let lh = presentation.map_or_else(|| self.ts_line(TS_BODY), |p| p.line_height);
let st = self.style;
let mut sp = Sp { value: self.m.edit.tb_natw, value2: lh, flags: 1, ..Sp::default() };
let (w, h) = if presentation.is_some() {
(self.dp(self.m.edit.tb_natw), lh.saturating_mul(5))
} else {
st.textbox_metrics(self, &mut sp)
};
self.place(w, h);
let mut hb = HB_FOCUS | HB_TEXT | CUR_IBEAM << HB_CURSOR_SHIFT;
if stflags & ST_DISABLED != 0 {
hb |= HB_DISABLED;
}
let ev = self.behave(id, hb);
let disabled = self.wp.ctx.last_state == SS_DISABLED as u32;
let mut editing = self.tb_focus();
if editing {
self.m.edit.ed_multi = true;
}
let hair = self.hair();
let r = self.wp.ctx.last_rect;
let x0 = sp.s[0].wrapping_add(r[0]);
let tw = r[2].wrapping_sub(sp.s[0].wrapping_add(sp.s[1])).max(1);
self.m.edit.ml_w = tw.wrapping_sub(hair); let top = r[1].wrapping_add(sp.s[2]);
let vh = r[3].wrapping_sub(sp.s[2].wrapping_add(sp.s[2])).max(lh); self.m.edit.ml_page = (if lh != 0 { vh / lh } else { 0 }).max(1);
let boff = if let Some(p) = presentation {
self.text.set_font(p.font, p.size);
p.baseline
} else {
self.ts_base(TS_BODY) };
let (font, size) = self.text.get_font();
self.m.edit.tb_font = font;
self.m.edit.tb_size = size;
let (mut sx, mut sy) = {
let s = self.slot(id);
(s.scrollx, s.scrolly)
};
self.tb_build_disp();
self.tbm_layout();
if editing && !self.spell_menu_open(id) {
let x = self.rtl_map(self.wp.ctx.mx, x0, tw).wrapping_sub(x0).wrapping_add(sx);
let y = self.wp.ctx.my.wrapping_sub(top).wrapping_add(sy);
self.tbm_mouse(ev, x, y, lh);
self.m.edit.ml_lh = lh;
self.tb_keys();
self.tb_typed();
#[cfg(target_os = "android")]
self.tb_android_ime();
if self.m.edit.tb_chg {
self.m.edit.tb_ev |= EV_ACT;
self.repass();
self.tb_build_disp();
}
self.tbm_layout();
if self.m.edit.ed_id != id {
editing = false; self.ime_off();
}
}
let ch = (self.m.edit.ml_start.len() as i32).wrapping_mul(lh);
let clamp_y = |y: i32| -> i32 {
let m = ch.wrapping_sub(vh).max(0);
y.min(m).max(0)
};
let wy = self.wp.ctx.wheel_y;
if ev & EV_HOVER != 0 && wy != 0 {
let d = wy.wrapping_mul(lh.wrapping_mul(3)) / 120;
let c = clamp_y(sy.wrapping_sub(d));
if c != sy {
sy = c;
self.wp.ctx.wheel_y = 0;
} }
let (_, pan) = self.pan_take(id);
sy = clamp_y(sy.wrapping_add(pan));
let hit_end = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
self.pan_view(id, hit_end.saturating_sub(1), super::touch::AX_V, [0, sy], [0, ch.wrapping_sub(vh).max(0)]);
let mut cl = 0;
let mut cx = 0;
if editing {
let caret = self.m.edit.ed_caret;
cl = self.tbm_line_of(caret);
let ls = self.m.edit.ml_start[cl];
cx = self.text.caret_x(&self.m.edit.buf[ls..], caret - ls);
let len = self.m.edit.buf.len() as i32;
let s = self.slot(id);
if caret as i32 != s.aux || len != s.fixed {
s.aux = caret as i32;
s.fixed = len;
let ct = (cl as i32).wrapping_mul(lh); if ct < sy {
sy = ct;
}
let cb = ct.wrapping_add(lh).wrapping_sub(vh);
if cb > sy {
sy = cb;
}
if self.m.edit.tb_flags & TB_WRAP == 0 {
let last = tw.wrapping_sub(hair); if cx.wrapping_sub(sx) > last {
sx = cx.wrapping_sub(last);
}
if cx < sx {
sx = cx;
}
}
}
}
sy = clamp_y(sy);
if self.m.edit.tb_flags & TB_WRAP != 0 {
sx = 0;
}
if sx < 0 {
sx = 0;
}
{
let s = self.slot(id);
s.scrollx = sx;
s.scrolly = sy;
}
sp.place(r);
sp.ev = ev;
sp.value = editing as i32;
sp.value2 = disabled as i32;
if let Some(p) = presentation {
self.draw_color(p.background);
self.draw_rect(r[0], r[1], r[2], r[3]);
} else {
st.textbox_frame(self, &sp);
}
let inset = if presentation.is_some() { 0 } else { hair };
self.draw_clip(x0, r[1].wrapping_add(inset), tw, r[3].wrapping_sub(inset).wrapping_sub(inset));
self.ltr_begin(x0, tw); let copy = ch > vh || self.m.edit.tb_flags & TB_WRAP == 0;
if copy {
self.scroll_copy_begin(id, x0.wrapping_sub(sx) as i64, top.wrapping_sub(sy) as i64);
}
let tcol = presentation.map_or_else(|| self.color(if disabled { T_TEXT_DISABLED } else { T_TEXT }), |p| p.color);
let scol = self.color(T_ON_ACCENT); let bx = x0.wrapping_sub(sx); let spell = self.spell_scan(id); if self.m.edit.buf.is_empty() {
if let Some(p) = placeholder {
let c = if presentation.is_some() { 0xff87918f } else { self.color(if disabled { T_TEXT_DISABLED } else { T_TEXT2 }) };
self.m.edit.tb_base = top.wrapping_sub(sy).wrapping_add(boff);
self.tb_emit_hint(x0, p, c, tw);
}
} else {
let (sa, sb) = if editing { self.tb_sel() } else { (0, 0) };
let n = self.m.edit.ml_start.len();
let first = if lh != 0 { sy / lh } else { 0 };
let mut last = (if lh != 0 { sy.wrapping_add(vh) / lh } else { 0 }).wrapping_add(1);
if last > n as i32 {
last = n as i32;
}
let mut l = first;
while l < last && (l as usize) < ML_MAX {
let ls = self.m.edit.ml_start[l as usize];
let le = self.tbm_line_end(l as usize);
let hy = self.m.edit.ml_hyph.get(l as usize).copied().unwrap_or(false);
let ly = l.wrapping_mul(lh).wrapping_add(top).wrapping_sub(sy); self.m.edit.tb_base = ly.wrapping_add(boff);
let s0 = sa.max(ls);
let s1 = sb.min(le);
let sel = sa != sb && (s0 < s1 || (s0 == s1 && sb > le));
if sel {
let x0r = self.text.caret_x(&self.m.edit.buf[ls..], s0 - ls);
let mut x1r = self.text.caret_x(&self.m.edit.buf[ls..], s1 - ls);
if sb > le {
x1r = x1r.wrapping_add(self.dp(4)); }
let c = self.color(T_ACCENT);
self.draw_color(c);
self.draw_rect(bx.wrapping_add(x0r), ly, x1r.wrapping_sub(x0r), lh);
self.tb_clip_x(r[0], bx.wrapping_add(x0r));
self.tbm_run(bx, ls, le, tcol, hy);
self.draw_unclip();
self.tb_clip_x(bx.wrapping_add(x0r), bx.wrapping_add(x1r));
self.tbm_run(bx, ls, le, scol, hy);
self.draw_unclip();
self.tb_clip_x(bx.wrapping_add(x1r), r[0].wrapping_add(r[2]));
self.tbm_run(bx, ls, le, tcol, hy);
self.draw_unclip();
} else {
self.tbm_run(bx, ls, le, tcol, hy);
}
if spell {
self.spell_draw(id, bx, ls, le, editing);
}
l += 1;
}
}
if editing {
let ly = (cl as i32).wrapping_mul(lh).wrapping_add(top).wrapping_sub(sy);
let x = bx.wrapping_add(cx);
if self.tb_caret_on() {
let c = presentation.map_or_else(|| self.color(T_TEXT), |p| p.color);
self.draw_color(c);
self.draw_rect(x, ly, hair, lh);
}
self.ime_pos(x, ly, lh);
}
if copy {
self.scroll_copy_end();
}
self.ltr_end();
self.draw_unclip();
if ch > vh {
sp.place(r);
sp.value = vh;
sp.value2 = ch;
sp.s[0] = sy;
sp.s[1] = top;
st.textbox_vbar(self, &sp);
}
if spell {
let hit = (ev & EV_RCLICK != 0).then(|| {
let x = self.rtl_map(self.wp.ctx.mx, x0, tw).wrapping_sub(x0).wrapping_add(sx);
let y = self.wp.ctx.my.wrapping_sub(top).wrapping_add(sy);
self.m.edit.ml_lh = lh;
self.tbm_hit(x, y)
});
self.spell_input(id, ev, hit, editing);
}
self.tb_finish(ev, editing)
}
fn tbm_run(&mut self, x: i32, ls: usize, le: usize, c: u32, hy: bool) {
let b = core::mem::take(&mut self.m.edit.buf);
if hy {
let mut t = b[ls..le].to_vec();
t.push(b'-');
self.tb_emit(x, &t, c);
} else {
self.tb_emit(x, &b[ls..le], c);
}
self.m.edit.buf = b;
}
fn tbm_layout(&mut self) {
if let Some(lines) = &self.m.edit.surface {
self.m.edit.ml_start = lines.iter().map(|l| Self::tb_fix_index(&self.m.edit.buf, l.start)).collect();
self.m.edit.ml_hyph.clear();
return;
}
let len = self.m.edit.buf.len();
let wrap = self.m.edit.tb_flags & TB_WRAP != 0;
let mut starts = core::mem::take(&mut self.m.edit.ml_start);
let mut hyph = core::mem::take(&mut self.m.edit.ml_hyph);
starts.clear();
hyph.clear();
let mut pos = 0;
'para: loop {
let mut pe = pos;
while pe < len && self.m.edit.buf[pe] != b'\n' {
pe += 1;
}
loop {
if starts.len() >= ML_MAX {
break 'para; }
starts.push(pos);
hyph.push(false);
if !wrap {
break;
}
let (e, hy) = self.tbm_wrap(pos, pe);
if e >= pe {
break;
}
if let Some(h) = hyph.last_mut() {
*h = hy;
}
pos = e; }
if pe >= len {
break;
}
pos = pe + 1; }
self.m.edit.ml_start = starts;
self.m.edit.ml_hyph = hyph;
}
fn tbm_wrap(&mut self, pos: usize, pe: usize) -> (usize, bool) {
if self.tw_fits(pos, pe - pos) {
return (pe, false);
}
let w = self.m.edit.ml_w;
let hw = self.text.width("-");
let hyph = self.m.edit.hyph.clone();
let text = &mut self.text;
match core::str::from_utf8(&self.m.edit.buf) {
Ok(s) => {
let mut fits = |a: usize, b: usize, h: bool| text.width_n(&s.as_bytes()[a..], b - a) + if h { hw } else { 0 } <= w;
match hyph.as_deref() {
Some(hy) => segment::wrap_end_hyph(s, pos, pe, &mut fits, Some(&mut |wd: &str, out: &mut Vec<usize>| *out = hy.hyphenate(wd))),
None => segment::wrap_end_hyph(s, pos, pe, &mut fits, None),
}
}
Err(_) => {
let buf = &self.m.edit.buf;
let mut best = u8_next(buf, pos);
while best < pe {
let n = u8_next(buf, best);
if text.width_n(&buf[pos..], n - pos) > w {
break;
}
best = n;
}
(best, false)
}
}
}
fn tw_fits(&mut self, pos: usize, n: usize) -> bool {
self.text.width_n(&self.m.edit.buf[pos..], n) <= self.m.edit.ml_w
}
fn tbm_line_of(&self, i: usize) -> usize {
let st = &self.m.edit.ml_start;
let mut l = st.len() as i32;
loop {
l -= 1;
if l <= 0 {
return 0;
}
if st[l as usize] <= i {
return l as usize;
}
}
}
fn tbm_line_end(&self, line: usize) -> usize {
if let Some(lines) = &self.m.edit.surface { return Self::tb_fix_index(&self.m.edit.buf, lines[line].end); }
let e = &self.m.edit;
if line + 1 >= e.ml_start.len() {
return e.buf.len();
}
let s = e.ml_start[line + 1];
if s != 0 && e.buf[s - 1] == b'\n' { s - 1 } else { s }
}
fn tbm_hit(&mut self, x: i32, y: i32) -> usize {
let y = y.max(0);
let lh = self.m.edit.ml_lh.max(1);
let n = self.m.edit.ml_start.len() as i32;
let l = (y / lh).min(n - 1).max(0) as usize;
let ls = self.m.edit.ml_start[l];
let le = self.tbm_line_end(l);
let i = ls + self.text.hit(&self.m.edit.buf[ls..], Some(le - ls), x);
self.tb_snap(i)
}
fn tbm_mouse(&mut self, ev: u32, x: i32, y: i32, lh: i32) {
self.m.edit.ml_lh = lh;
let c = &self.wp.ctx;
if c.pressed & (1 << BTN_LEFT) != 0 && ev & EV_HOVER != 0 && c.active == self.m.edit.tb_id {
self.m.edit.ed_prefx = -1;
let i = self.tbm_hit(x, y);
self.tb_press(ev, i);
} else if ev & EV_HELD != 0 {
if self.m.edit.ed_drag {
let i = self.tbm_hit(x, y);
self.tb_drag_to(i);
}
} else {
self.m.edit.ed_drag = false;
}
}
fn tbm_vmove(&mut self, lines: i32, shift: bool) {
if self.m.edit.surface.is_some() { self.surface_vmove(lines, shift); return; }
self.tbm_layout();
let caret = self.m.edit.ed_caret;
let cur = self.tbm_line_of(caret);
let mut px = self.m.edit.ed_prefx;
if px < 0 {
let ls = self.m.edit.ml_start[cur];
px = self.text.caret_x(&self.m.edit.buf[ls..], caret - ls);
}
let t = (cur as i32).wrapping_add(lines);
let pos = if t < 0 {
0 } else if t >= self.m.edit.ml_start.len() as i32 {
self.m.edit.buf.len() } else {
let ls = self.m.edit.ml_start[t as usize];
let le = self.tbm_line_end(t as usize);
let i = ls + self.text.hit(&self.m.edit.buf[ls..], Some(le - ls), px);
self.tb_snap(i)
};
self.tb_move(pos, shift);
self.m.edit.ed_prefx = px;
}
fn tb_emit_hint(&mut self, x: i32, p: &str, c: u32, w: i32) {
let e = &self.m.edit;
self.text.set_font(e.tb_font, e.tb_size);
if w > 0 && self.text.width(p) > w {
let (cut, _) = self.ellipsize(p.as_bytes(), w);
self.tb_emit(x, cut.as_bytes(), c);
} else {
self.tb_emit(x, p.as_bytes(), c);
}
}
fn tb_emit(&mut self, x: i32, s: &[u8], c: u32) {
let e = &self.m.edit;
let (y, font, size) = (e.tb_base, e.tb_font, e.tb_size);
self.cmd_text(x, y, s, font, size, c, 0);
if self.m.edit.presentation.is_some_and(|p| p.underline) {
self.text.set_font(font, size);
let width = (self.text.measure(core::str::from_utf8(s).unwrap_or("")) + 128) / 256;
self.draw_color(c);
self.draw_rect(x, y + (size / 256 / 12).max(1), width, (size / 256 / 16).max(1));
}
}
fn tb_emit_disp(&mut self, x: i32, c: u32) {
let pw = self.m.edit.tb_flags & TB_PASSWORD != 0;
let d = core::mem::take(if pw { &mut self.m.edit.disp } else { &mut self.m.edit.buf });
self.tb_emit(x, &d, c);
*(if pw { &mut self.m.edit.disp } else { &mut self.m.edit.buf }) = d;
}
fn tb_clip_x(&mut self, x0: i32, x1: i32) {
let r = self.wp.ctx.last_rect;
self.draw_clip(x0, r[1], x1.wrapping_sub(x0), r[3]);
}
fn tb_caret_on(&mut self) -> bool {
let (a, b) = self.tb_sel();
if a != b {
return false; }
if self.headless {
return true; }
let blink = self.blink_ms; if blink == 0 {
return true;
}
let t = self.now_ms.wrapping_sub(self.m.edit.ed_t0);
let phase = t / blink;
self.timer(UIT_CARET, blink - t % blink);
phase & 1 == 0
}
pub fn ime_pos(&mut self, x: i32, y: i32, h: i32) {
let px = self.phys_x(x);
self.a11y_caret(px, y, h);
if !self.headless {
plat::ime_pos(px, y, h);
}
self.m.edit.ime_frame = (h > 0).then_some(self.frame);
}
#[cfg(target_os = "android")]
pub(crate) fn ime_frame_end(&mut self) {
if self.m.edit.ime_frame.is_some_and(|f| f != self.frame) {
self.ime_off();
}
}
fn ime_off(&mut self) {
#[cfg(target_os = "android")]
plat::android_ime_clear();
self.ime_pos(0, 0, 0);
}
pub(crate) fn tb_rekey(&mut self, old: u32, new: u32) {
let e = &mut self.m.edit;
if e.ed_id == old {
e.ed_id = new;
}
if e.ed_blur == old {
e.ed_blur = new;
}
}
fn tb_focus(&mut self) -> bool {
let id = self.m.edit.tb_id;
if self.m.edit.ed_blur == id {
self.m.edit.ed_blur = 0;
self.m.edit.tb_ev |= EV_COMMIT;
}
if self.wp.ctx.focus == id && self.wp.ctx.last_state != SS_DISABLED as u32 && self.uiw_cur == self.m.win.active {
let fv = self.wp.ctx.focus_visible;
let e = &mut self.m.edit;
if e.ed_id != id {
if e.ed_id != 0 {
e.ed_blur = e.ed_id;
}
e.ed_id = id;
e.ed_caret = e.buf.len();
e.ed_anchor = e.buf.len();
if fv {
e.ed_anchor = 0; }
e.ed_drag = false;
self.ud_reset();
self.tb_blink_reset();
}
self.m.edit.ed_seen = self.frame;
let e = &mut self.m.edit;
e.ed_caret = Self::tb_fix_index(&e.buf, e.ed_caret);
e.ed_anchor = Self::tb_fix_index(&e.buf, e.ed_anchor);
return true;
}
if self.m.edit.ed_id == id {
self.m.edit.ed_id = 0;
self.m.edit.tb_ev |= EV_COMMIT;
self.ime_off();
}
false
}
fn tb_fix_index(buf: &[u8], i: usize) -> usize {
u8_clip(buf, i.min(buf.len()))
}
fn tb_blink_reset(&mut self) {
self.m.edit.ed_t0 = self.now_ms;
}
fn tb_move(&mut self, pos: usize, shift: bool) {
let e = &mut self.m.edit;
e.ed_caret = pos;
if !shift {
e.ed_anchor = pos;
}
e.ed_merge = false;
self.tb_blink_reset();
}
fn tb_sel(&self) -> (usize, usize) {
let e = &self.m.edit;
if e.ed_caret <= e.ed_anchor { (e.ed_caret, e.ed_anchor) } else { (e.ed_anchor, e.ed_caret) }
}
fn tb_press(&mut self, ev: u32, i: usize) {
self.m.edit.ed_drag = true;
self.m.edit.ed_merge = false;
self.tb_blink_reset();
if ev & EV_DOUBLE != 0 {
self.tb_word_at(i);
self.m.edit.ed_drag = false;
return;
}
self.m.edit.ed_caret = i;
if self.mods & MOD_SHIFT == 0 {
self.m.edit.ed_anchor = i; }
}
fn tb_drag_to(&mut self, i: usize) {
if i != self.m.edit.ed_caret {
self.m.edit.ed_caret = i;
self.tb_blink_reset();
}
}
fn tb_mouse(&mut self, ev: u32, x: i32) {
let c = &self.wp.ctx;
if c.pressed & (1 << BTN_LEFT) != 0 && ev & EV_HOVER != 0 && c.active == self.m.edit.tb_id {
let i = self.tb_hit(x);
self.tb_press(ev, i);
} else if ev & EV_HELD != 0 {
if self.m.edit.ed_drag {
let i = self.tb_hit(x);
self.tb_drag_to(i);
}
} else {
self.m.edit.ed_drag = false;
}
}
fn tb_hit(&mut self, x: i32) -> usize {
let e = &self.m.edit;
let d = disp_of(e);
let i = self.text.hit(d, Some(d.len()), x);
let i = self.tb_from_disp(i);
self.tb_snap(i)
}
fn tb_build_disp(&mut self) {
let e = &mut self.m.edit;
if e.tb_flags & TB_PASSWORD == 0 {
return;
}
e.disp.clear();
let mut i = 0;
let mut n = 0;
while at(&e.buf, i) != 0 && n < PW_MAX {
e.disp.extend_from_slice(&[0xE2, 0x97, 0x8F]); n += 1;
i = u8_next(&e.buf, i);
}
}
fn tb_to_disp(&self, i: usize) -> usize {
let e = &self.m.edit;
if e.tb_flags & TB_PASSWORD == 0 {
return i;
}
let mut p = 0;
let mut n = 0;
while p < i && at(&e.buf, p) != 0 && n < PW_MAX {
p = u8_next(&e.buf, p);
n += 1;
}
n * 3
}
fn tb_from_disp(&self, d: usize) -> usize {
let e = &self.m.edit;
if e.tb_flags & TB_PASSWORD == 0 {
return d;
}
let mut n = d / 3;
let mut p = 0;
while n != 0 && at(&e.buf, p) != 0 {
p = u8_next(&e.buf, p);
n -= 1;
}
p
}
fn tb_str(&self) -> Option<&str> {
core::str::from_utf8(&self.m.edit.buf).ok()
}
fn tb_prev(&self, i: usize) -> usize {
match self.tb_str() {
Some(s) => segment::grapheme_prev(s, i),
None => u8_prev(&self.m.edit.buf, i),
}
}
fn tb_next(&self, i: usize) -> usize {
match self.tb_str() {
Some(s) => segment::grapheme_next(s, i),
None => u8_next(&self.m.edit.buf, i),
}
}
fn tb_back(&self, i: usize) -> usize {
let p = self.tb_prev(i);
match self.tb_str() {
Some(s) if !segment::pictographic_cluster(s, p, i) => u8_prev(&self.m.edit.buf, i).max(p),
_ => p,
}
}
fn tb_snap(&self, i: usize) -> usize {
match self.tb_str() {
Some(s) if i < s.len() && !segment::is_grapheme_boundary(s, i) => segment::grapheme_prev(s, i),
_ => i,
}
}
fn tb_word_left(&self, i: usize) -> usize {
if self.m.edit.tb_flags & TB_PASSWORD != 0 {
return 0;
}
match self.tb_str() {
Some(s) => segment::word_prev(s, i),
None => 0,
}
}
fn tb_word_right(&self, i: usize) -> usize {
let len = self.m.edit.buf.len();
if self.m.edit.tb_flags & TB_PASSWORD != 0 || i >= len {
return len;
}
match self.tb_str() {
Some(s) => segment::word_next(s, i),
None => len,
}
}
fn tb_word_at(&mut self, i: usize) {
let len = self.m.edit.buf.len();
if len == 0 {
self.m.edit.ed_anchor = 0;
self.m.edit.ed_caret = 0;
return;
}
if self.m.edit.tb_flags & TB_PASSWORD != 0 {
self.m.edit.ed_anchor = 0;
self.m.edit.ed_caret = len;
return;
}
let (a, b) = match self.tb_str() {
Some(s) => {
let (mut a, mut b) = segment::word_at(s, i);
let kind = segment::word_kind(s, a, b);
if kind != segment::WK_WORD {
while a > 0 {
let (pa, pb) = segment::word_at(s, a - 1);
if segment::word_kind(s, pa, pb) != kind || s[pa..pb].contains('\n') {
break;
}
a = pa;
}
while b < s.len() {
let (na, nb) = segment::word_at(s, b);
if segment::word_kind(s, na, nb) != kind || s[na..nb].contains('\n') {
break;
}
b = nb;
}
}
(a, b)
}
None => (0, len),
};
self.m.edit.ed_anchor = a;
self.m.edit.ed_caret = b;
}
fn tb_delete(&mut self, a: usize, b: usize) {
let e = &mut self.m.edit;
let b = b.min(e.buf.len());
if b <= a {
return;
}
e.buf.drain(a..b);
if e.surface.is_some() { e.surface_start = Some(e.surface_start.map_or(a, |v| v.min(a))); }
e.tb_chg = true;
}
fn tb_insert(&mut self, pos: usize, src: &[u8]) -> usize {
let e = &mut self.m.edit;
if e.tb_max <= e.buf.len() {
return 0;
}
let room = e.tb_max - e.buf.len();
let n = if src.len() > room { u8_clip(src, room) } else { src.len() };
if n == 0 {
return 0;
}
e.buf.splice(pos..pos, src[..n].iter().copied());
if e.surface.is_some() { e.surface_start = Some(e.surface_start.map_or(pos, |v| v.min(pos))); }
e.tb_chg = true;
n
}
fn tb_replace(&mut self, src: &[u8]) {
let (a, b) = self.tb_sel();
if self.m.edit.surface.is_some()
&& self.m.edit.buf.len().saturating_sub(b - a).saturating_add(src.len()) > self.m.edit.tb_max
{
return;
}
if a != b {
self.tb_delete(a, b);
}
let mut p = a;
self.m.edit.ed_caret = p;
self.m.edit.ed_anchor = p;
if src.is_empty() {
return;
}
p += self.tb_insert(p, src);
self.m.edit.ed_caret = p;
self.m.edit.ed_anchor = p;
}
fn tb_typed(&mut self) {
let cps = self.text_take(TXQ_MAX);
let flags = self.m.edit.tb_flags;
if flags & TB_READONLY != 0 {
return;
}
let mut stage = Vec::new();
for &c in cps.iter() {
if tb_accept(c, flags) {
u8_push(&mut stage, c);
}
}
if stage.is_empty() {
return;
}
if !self.m.edit.ed_merge {
self.ud_push(); }
self.tb_replace(&stage);
self.m.edit.ed_merge = true;
self.tb_blink_reset();
}
#[cfg(target_os = "android")]
fn tb_android_ime(&mut self) {
let Some((bytes, anchor, caret)) = plat::android_ime_take(self.m.edit.tb_id) else { return; };
if self.m.edit.tb_flags & TB_READONLY != 0 { return; }
let mut filtered = Vec::new();
let mut positions = [0; 2];
for (i, c) in core::str::from_utf8(&bytes).unwrap_or("").char_indices() {
if tb_accept(c as u32, self.m.edit.tb_flags) { u8_push(&mut filtered, c as u32); }
if i < anchor { positions[0] = filtered.len(); }
if i < caret { positions[1] = filtered.len(); }
}
if filtered != self.m.edit.buf {
if !self.m.edit.ed_merge { self.ud_push(); }
self.m.edit.ed_anchor = 0;
self.m.edit.ed_caret = self.m.edit.buf.len();
self.tb_replace(&filtered);
self.m.edit.ed_merge = true;
}
self.m.edit.ed_anchor = Self::tb_fix_index(&self.m.edit.buf, positions[0]);
self.m.edit.ed_caret = Self::tb_fix_index(&self.m.edit.buf, positions[1]);
self.m.edit.ed_prefx = -1;
self.tb_blink_reset();
self.repass();
}
pub(crate) fn text_key_filter(&mut self, k: Option<usize>) -> Option<usize> {
let i = k?;
let e = &self.m.edit;
if e.ed_self {
return k; }
if e.ed_id == 0 || e.ed_id != self.wp.ctx.focus {
return k;
}
if self.frame.wrapping_sub(e.ed_seen) > 2 {
return k; }
if self.spell_menu_open(e.ed_id) {
return k; }
let q = self.wp.ctx.keyq[i];
if tb_owns(q, e.ed_multi) && !self.tb_esc_passes(q) { None } else { k }
}
fn tb_esc_passes(&self, k: u32) -> bool {
k & 0xFFFF == VK_ESCAPE && self.wp.ctx.kb.soft != 0 && self.wp.ctx.kb.soft == self.wp.ctx.focus
}
fn tb_keys(&mut self) {
self.m.edit.ed_self = true;
loop {
if self.m.edit.ed_id != self.m.edit.tb_id {
break;
}
let k = self.key_first();
if k == 0 || !tb_owns(k, self.m.edit.ed_multi) || self.tb_esc_passes(k) {
break;
}
self.key(k & 0xFFFF); let k = self.rtl_text_key(k); let vk = k & 0xFFFF;
let m = k >> 16;
let shift = m & MOD_SHIFT != 0;
let ctrl = m & (MOD_ALT | MOD_ALTGR) == 0 && m & MOD_CTRL != 0; let flags = self.m.edit.tb_flags;
if flags & TB_MULTI != 0 {
let page = self.m.edit.ml_page;
match vk {
VK_UP => {
self.tbm_vmove(-1, shift);
continue;
}
VK_DOWN => {
self.tbm_vmove(1, shift);
continue;
}
VK_PRIOR => {
self.tbm_vmove(page.wrapping_neg(), shift);
continue;
}
VK_NEXT => {
self.tbm_vmove(page, shift);
continue;
}
VK_RETURN => {
if ctrl {
self.m.edit.tb_ev |= EV_COMMIT; } else if flags & TB_READONLY == 0 {
self.m.edit.ed_prefx = -1;
self.ud_push();
self.tb_replace(b"\n");
self.tb_blink_reset();
}
continue;
}
VK_HOME if !ctrl => {
self.m.edit.ed_prefx = -1;
self.tbm_layout();
let l = self.tbm_line_of(self.m.edit.ed_caret);
let p = self.m.edit.ml_start[l];
self.tb_move(p, shift);
continue;
}
VK_END if !ctrl => {
self.m.edit.ed_prefx = -1;
self.tbm_layout();
let l = self.tbm_line_of(self.m.edit.ed_caret);
let p = self.tbm_line_end(l);
self.tb_move(p, shift);
continue;
}
_ => {} }
}
self.m.edit.ed_prefx = -1;
let len = self.m.edit.buf.len();
let caret = self.m.edit.ed_caret;
match vk {
VK_LEFT => {
let (a, b) = self.tb_sel();
let p = if ctrl {
self.tb_word_left(caret)
} else if !shift && a != b {
a } else {
self.tb_prev(caret)
};
self.tb_move(p, shift);
}
VK_RIGHT => {
let (a, b) = self.tb_sel();
let p = if ctrl {
self.tb_word_right(caret)
} else if !shift && a != b {
b } else {
self.tb_next(caret)
};
self.tb_move(p, shift);
}
VK_HOME => self.tb_move(0, shift),
VK_END => self.tb_move(len, shift),
VK_BACK => {
if flags & TB_READONLY != 0 {
continue;
}
let (a, b) = self.tb_sel();
if a != b {
self.tb_delrange(a, b);
} else {
let s = if ctrl { self.tb_word_left(a) } else { self.tb_back(a) };
self.tb_delrange(s, a);
}
}
VK_DELETE => {
if shift && !ctrl {
self.tb_cut(); continue;
}
if flags & TB_READONLY != 0 {
continue;
}
let (a, b) = self.tb_sel();
if a != b {
self.tb_delrange(a, b);
} else {
let e = if ctrl { self.tb_word_right(a) } else { self.tb_next(a) };
self.tb_delrange(a, e);
}
}
VK_INSERT => {
if ctrl {
self.tb_copy(); } else if shift && flags & TB_READONLY == 0 {
self.tb_paste(); }
}
VK_RETURN => self.m.edit.tb_ev |= EV_COMMIT,
VK_ESCAPE => {
self.wp.ctx.focus = 0;
self.m.edit.ed_id = 0;
self.m.edit.tb_ev |= EV_COMMIT;
self.repass();
break;
}
_ if !ctrl => {} 65 => {
let e = &mut self.m.edit;
e.ed_anchor = 0;
e.ed_caret = len;
e.ed_merge = false;
}
67 => self.tb_copy(),
88 => self.tb_cut(),
86 => {
if flags & TB_READONLY == 0 {
self.tb_paste();
}
}
90 | 89 => {
if flags & TB_READONLY == 0 {
if vk == 90 && !shift {
self.ud_undo();
} else {
self.ud_redo();
}
}
}
_ => {}
}
}
self.m.edit.ed_self = false;
}
fn tb_delrange(&mut self, a: usize, b: usize) {
if a >= b {
return;
}
self.ud_push();
self.tb_delete(a, b);
self.tb_move(a, false);
}
fn tb_cut(&mut self) {
if self.m.edit.tb_flags & (TB_READONLY | TB_PASSWORD) != 0 {
return;
}
self.tb_copy();
let (a, b) = self.tb_sel();
if a != b {
self.tb_delrange(a, b);
}
}
fn tb_copy(&mut self) {
if self.m.edit.tb_flags & TB_PASSWORD != 0 {
return;
}
let (a, b) = self.tb_sel();
if a == b {
return;
}
let s = self.m.edit.buf[a..b].to_vec();
self.clip_set(CLIP_TEXT, &s);
}
fn tb_paste(&mut self) {
let Some(clip) = self.clip_get(CLIP_TEXT) else { return };
let flags = self.m.edit.tb_flags;
let cap = clip.len() * 3; let mut out = Vec::with_capacity(cap + 4);
let mut i = 0;
loop {
let (c, n) = u8_decode(&clip, i);
if c == 0 {
break;
}
i = n;
if c == 10 {
if flags & TB_MULTI == 0 || out.len() >= cap {
break;
}
out.push(10); continue;
}
if c == 13 {
if flags & TB_MULTI != 0 {
if self.m.edit.surface.is_some() && clip.get(i) != Some(&10) {
out.push(10); }
continue; }
break;
}
if tb_accept(c, flags) {
u8_push(&mut out, c);
}
}
if out.is_empty() {
return;
}
self.ud_push();
self.tb_replace(&out);
self.m.edit.ed_merge = false;
self.tb_blink_reset();
}
pub fn clip_set_many(&mut self, entries: &[(u32, &[u8])]) -> i32 {
if !plat::clip_many_valid(entries) { return -1; }
if !self.headless && !self.hr_replaying() { return if plat::clip_set_many(entries) { 0 } else { -1 }; }
let rich: Vec<_> = entries.iter().filter(|e| e.0 != CLIP_TEXT).map(|e| (e.0, e.1.to_vec())).collect();
let text = entries.iter().find(|e| e.0 == CLIP_TEXT).map(|e| e.1.to_vec());
self.hclip_img_clear();
self.m.edit.hclip = text;
self.m.edit.hclip_fmt = CLIP_TEXT;
self.m.edit.hclip_rich = rich;
0
}
pub fn clip_set(&mut self, fmt: u32, data: &[u8]) -> i32 {
if matches!(fmt, CLIP_HTML | CLIP_RTF | CLIP_DOCX) { return self.clip_set_many(&[(fmt, data)]); }
if !self.headless && !self.hr_replaying() {
return if plat::clip_set(fmt, data) { 0 } else { -1 };
}
if fmt != CLIP_TEXT {
return self.hclip_img_set(fmt, data); }
self.hclip_img_clear(); self.m.edit.hclip_rich.clear();
self.m.edit.hclip = Some(data.to_vec());
self.m.edit.hclip_fmt = fmt;
0
}
pub fn clip_get(&mut self, fmt: u32) -> Option<Vec<u8>> {
if self.hr_on() {
return self.hr_clip_get(fmt); }
self.clip_read(fmt)
}
pub(crate) fn clip_read(&mut self, fmt: u32) -> Option<Vec<u8>> {
if !self.headless {
return plat::clip_get(fmt);
}
if fmt != CLIP_TEXT {
if matches!(fmt, CLIP_HTML | CLIP_RTF | CLIP_DOCX) {
return self.m.edit.hclip_rich.iter().find(|e| e.0 == fmt).map(|e| e.1.clone());
}
return self.hclip_img_get(fmt); }
match &self.m.edit.hclip {
Some(d) if self.m.edit.hclip_fmt == fmt => Some(d.clone()),
_ => None,
}
}
pub fn open_folder(&mut self, path: &str) -> i32 {
if !self.headless && !self.hr_replaying() {
return plat::open_folder(path);
}
plat::write_stdout(format!("ui_open_folder: {}\n", path).as_bytes());
0
}
pub fn open_url(&mut self, url: &str) -> i32 {
if !self.headless && !self.hr_replaying() {
return plat::open_url(url);
}
plat::write_stdout(format!("ui_open_url: {}\n", url).as_bytes());
0
}
pub(crate) fn hclip_text_has(&self) -> bool {
self.m.edit.hclip.is_some()
}
pub(crate) fn hclip_text_clear(&mut self) {
self.m.edit.hclip = None;
self.m.edit.hclip_rich.clear();
}
fn ud_reset(&mut self) {
let e = &mut self.m.edit;
e.ud_base = 0;
e.ud_pos = 0;
e.ud_count = 0;
e.ed_merge = false;
}
fn ud_slot(&self, i: usize) -> usize {
(self.m.edit.ud_base + i) & (UD_N - 1)
}
fn ud_room(&mut self) {
let e = &mut self.m.edit;
if e.ud_count < UD_N {
return;
}
e.ud_base = (e.ud_base + 1) & (UD_N - 1);
e.ud_count -= 1;
if e.ud_pos != 0 {
e.ud_pos -= 1;
}
}
fn ud_store(&mut self, i: usize) {
let k = self.ud_slot(i);
let e = &mut self.m.edit;
e.ud_hdr[k] = [e.buf.len(), e.ed_caret, e.ed_anchor];
e.ud_data[k].clear();
e.ud_data[k].extend_from_slice(&e.buf);
}
fn ud_restore(&mut self, i: usize) {
let k = self.ud_slot(i);
let e = &mut self.m.edit;
let [len, caret, anchor] = e.ud_hdr[k];
let n = len.min(e.tb_max);
e.buf.clear();
e.buf.extend_from_slice(&e.ud_data[k][..n]);
if e.surface.is_some() { e.surface_start = Some(0); }
e.tb_chg = true;
e.ed_merge = false;
e.ed_anchor = Self::tb_fix_index(&e.buf, anchor);
e.ed_caret = Self::tb_fix_index(&e.buf, caret);
self.tb_blink_reset();
}
fn ud_push(&mut self) {
self.m.edit.ed_merge = false;
if self.m.edit.buf.len() >= UD_SEG {
self.ud_reset(); return;
}
self.m.edit.ud_count = self.m.edit.ud_pos;
self.ud_room();
self.ud_store(self.m.edit.ud_pos);
self.m.edit.ud_pos += 1;
self.m.edit.ud_count = self.m.edit.ud_pos;
}
fn ud_undo(&mut self) -> bool {
if self.m.edit.ud_pos == 0 {
return false;
}
if self.m.edit.ud_pos == self.m.edit.ud_count && self.m.edit.buf.len() < UD_SEG {
self.ud_room();
self.ud_store(self.m.edit.ud_count);
self.m.edit.ud_count += 1;
}
self.m.edit.ud_pos -= 1;
self.ud_restore(self.m.edit.ud_pos);
true
}
fn ud_redo(&mut self) -> bool {
let p = self.m.edit.ud_pos + 1;
if p >= self.m.edit.ud_count {
return false;
}
self.m.edit.ud_pos = p;
self.ud_restore(p);
true
}
#[cfg_attr(jfgui_dev, track_caller)]
pub fn number_box(&mut self, id: u32, v: &mut i32, rg: &Range) -> u32 {
let _src = self.src_scope();
self.nb_pend_store(id, Some(&mut *v), None, Some(rg), None);
let entry = *v;
let mut deferred = 0;
let step = if rg.step >= 1 { rg.step } else { 1 };
let mut flags = NB_CAP | TB_DIGITS;
if rg.min < 0 {
flags |= TB_SIGNED;
}
if self.m.edit.nb_pend == id {
self.m.edit.nb_pend = 0;
deferred = self.m.edit.nb_pend_ev;
}
let view;
if self.wp.ctx.focus == id && self.uiw_cur == self.m.win.active {
if self.m.edit.nb_id != id {
self.nb_release(); let e = &mut self.m.edit;
e.nb_id = id;
e.nb_kind = 0;
e.nb_buf = nb_format(*v);
self.nb_sync(*v as u32 as u64); }
self.m.edit.nb_rg = *rg; self.m.edit.nb_seen = self.frame;
self.nb_follow(*v as u32 as u64, 0); if self.m.edit.ed_id == id {
let n = self.nb_step_keys(); if n != 0 {
let d = n.wrapping_mul(step);
let p = nb_parse_text(&self.m.edit.nb_buf, *v);
self.nb_store(p.wrapping_add(d as i64), v);
self.repass();
}
}
view = false;
} else if self.m.edit.nb_id == id {
self.m.edit.nb_rg = *rg;
view = false; } else {
self.m.edit.nb_view = nb_format(*v);
view = true;
}
let ev = self.nb_core(id, flags, view);
if ev & EV_COMMIT != 0 && self.m.edit.nb_id == id {
if self.nb_edited() {
let p = nb_parse_text(&self.m.edit.nb_buf, *v);
self.nb_store(p, v);
self.repass();
}
if self.wp.ctx.focus != id {
self.m.edit.nb_id = 0;
}
}
let mut out = (ev & !EV_ACT) | deferred; if *v != entry {
out |= EV_ACT;
}
self.tb_sem_set(*v, rg.max);
out
}
fn nb_core(&mut self, id: u32, flags: u32, view: bool) -> u32 {
self.m.edit.tb_natw = NB_NAT_W;
let mut b = core::mem::take(if view { &mut self.m.edit.nb_view } else { &mut self.m.edit.nb_buf });
let ev = self.tb_core(id, None, &mut b, flags, 0);
*(if view { &mut self.m.edit.nb_view } else { &mut self.m.edit.nb_buf }) = b;
ev
}
fn nb_step_keys(&mut self) -> i32 {
self.m.edit.ed_self = true;
let mut n = self.key(VK_UP) as i32;
n -= self.key(VK_DOWN) as i32;
n += self.key(VK_PRIOR) as i32 * 10;
n -= self.key(VK_NEXT) as i32 * 10;
self.m.edit.ed_self = false;
n
}
#[cfg_attr(jfgui_dev, track_caller)]
pub fn number_box_f(&mut self, id: u32, v: &mut f64, rf: &RangeF) -> u32 {
let _src = self.src_scope();
self.nb_pend_store(id, None, Some(&mut *v), None, Some(rf));
let entry = v.to_bits();
let mut deferred = 0;
let mut flags = NB_CAP | TB_DIGITS | TB_DECIMAL;
if 0.0 > rf.min {
flags |= TB_SIGNED;
}
if self.m.edit.nb_pend == id {
self.m.edit.nb_pend = 0;
deferred = self.m.edit.nb_pend_ev;
}
let view;
if self.wp.ctx.focus == id && self.uiw_cur == self.m.win.active {
if self.m.edit.nb_id != id {
self.nb_release();
let t = self.nbf_format(*v, rf.dec);
let e = &mut self.m.edit;
e.nb_id = id;
e.nb_kind = 1;
e.nb_buf = t;
self.nb_sync(v.to_bits()); }
self.m.edit.nb_rf = *rf;
self.m.edit.nb_seen = self.frame;
self.nb_follow(v.to_bits(), rf.dec); if self.m.edit.ed_id == id {
let n = self.nb_step_keys();
if n != 0 {
let x = self.nbf_parse(*v);
let mut step = rf.step;
if !(step > 0.0) {
step = (rf.max - rf.min) * 0.01;
}
let d = n as f64 * step;
self.nbf_store(x + d, v);
self.repass();
}
}
view = false;
} else if self.m.edit.nb_id == id {
self.m.edit.nb_rf = *rf;
view = false; } else {
let t = self.nbf_format(*v, rf.dec);
self.m.edit.nb_view = t;
view = true;
}
let ev = self.nb_core(id, flags, view);
if ev & EV_COMMIT != 0 && self.m.edit.nb_id == id {
if self.nb_edited() {
let x = self.nbf_parse(*v);
self.nbf_store(x, v);
self.repass();
}
if self.wp.ctx.focus != id {
self.m.edit.nb_id = 0;
}
}
let mut out = (ev & !EV_ACT) | deferred;
if v.to_bits() != entry {
out |= EV_ACT;
}
let sv = rf_scaled(rf, *v);
let mx = rf_scaled(rf, rf.max);
self.tb_sem_set(sv, mx);
out
}
#[cfg_attr(jfgui_dev, track_caller)]
pub fn nb_live(&mut self, id: u32, v: &mut i32) -> bool {
let _src = self.src_scope();
let e = &self.m.edit;
if id == 0 || e.nb_id != id || e.ed_id != id || e.nb_kind != 0 {
return false;
}
let x = rg_clamp(nb_parse_text(&e.nb_buf, *v), &e.nb_rg); if x == *v {
return false;
}
*v = x;
true
}
#[cfg_attr(jfgui_dev, track_caller)]
pub fn nb_live_f(&mut self, id: u32, v: &mut f64) -> bool {
let _src = self.src_scope();
let e = &self.m.edit;
if id == 0 || e.nb_id != id || e.ed_id != id || e.nb_kind != 1 {
return false;
}
let Some(x) = parse_f64_sep(&e.nb_buf, self.m.value.decimal_sep) else { return false };
let x = rf_fix(&e.nb_rf, x);
if x.to_bits() == v.to_bits() {
return false;
}
*v = x;
true
}
fn nbf_format(&self, v: f64, dec: u32) -> Vec<u8> {
fmt_f64_sep(v, dec, self.m.value.decimal_sep).into_bytes()
}
fn nbf_parse(&self, stored: f64) -> f64 {
parse_f64_sep(&self.m.edit.nb_buf, self.m.value.decimal_sep).unwrap_or(stored)
}
fn nbf_store(&mut self, x: f64, v: &mut f64) {
let rf = self.m.edit.nb_rf;
*v = rf_fix(&rf, x);
let t = self.nbf_format(*v, rf.dec);
self.m.edit.nb_buf = t;
self.nb_sync(v.to_bits()); self.nb_caret_end();
}
fn nb_store(&mut self, x: i64, v: &mut i32) {
*v = rg_clamp(x, &self.m.edit.nb_rg);
self.m.edit.nb_buf = nb_format(*v);
self.nb_sync(*v as u32 as u64); self.nb_caret_end();
}
fn nb_caret_end(&mut self) {
let e = &mut self.m.edit;
if e.nb_id != 0 && e.ed_id == e.nb_id {
e.ed_caret = e.nb_buf.len();
e.ed_anchor = e.nb_buf.len();
}
}
fn nb_sync(&mut self, shown: u64) {
let e = &mut self.m.edit;
e.nb_orig.clear();
e.nb_orig.extend_from_slice(&e.nb_buf[..e.nb_buf.len().min(NB_CAP as usize - 1)]);
e.nb_shown = shown;
}
fn nb_edited(&self) -> bool {
self.m.edit.nb_buf != self.m.edit.nb_orig
}
fn nb_follow(&mut self, cur: u64, dec: u32) {
if cur == self.m.edit.nb_shown || self.nb_edited() {
return; }
let e = &self.m.edit;
let old = e.nb_buf.len();
let (lo, hi) = if e.ed_caret >= e.ed_anchor { (e.ed_anchor, e.ed_caret) } else { (e.ed_caret, e.ed_anchor) };
let all = lo == 0 && hi == old && old != 0;
let t = if e.nb_kind == 0 { nb_format(cur as u32 as i32) } else { self.nbf_format(f64::from_bits(cur), dec) };
self.m.edit.nb_buf = t;
self.nb_sync(cur);
let e = &mut self.m.edit;
if e.ed_id == e.nb_id {
e.ed_caret = e.nb_buf.len();
e.ed_anchor = e.nb_buf.len();
if all {
e.ed_anchor = 0;
}
}
}
fn nb_release(&mut self) {
let old = self.m.edit.nb_id;
if old == 0 {
return;
}
self.m.edit.nb_id = 0;
if self.frame.wrapping_sub(self.m.edit.nb_seen) > 2 {
return; }
if self.nb_edited() {
let e = &mut self.m.edit;
e.pend_text = Some((old, e.nb_kind, e.nb_buf.clone()));
self.repass();
}
self.m.edit.nb_pend = old;
self.m.edit.nb_pend_ev = EV_COMMIT;
}
fn nb_pend_store(&mut self, id: u32, vi: Option<&mut i32>, vf: Option<&mut f64>, rg: Option<&Range>, rf: Option<&RangeF>) {
match &self.m.edit.pend_text {
Some((pid, _, _)) if *pid == id => {}
_ => return,
}
let Some((_, kind, t)) = self.m.edit.pend_text.take() else { return };
let changed = match (kind, vi, vf, rg, rf) {
(0, Some(v), _, Some(rg), _) => {
let old = *v;
*v = rg_clamp(nb_parse_text(&t, *v), rg);
*v != old
}
(1, _, Some(v), _, Some(rf)) => {
let old = v.to_bits();
let x = parse_f64_sep(&t, self.m.value.decimal_sep).unwrap_or(*v);
*v = rf_fix(rf, x);
v.to_bits() != old
}
_ => false,
};
if changed && self.m.edit.nb_pend == id {
self.m.edit.nb_pend_ev |= EV_ACT;
}
}
}
impl Ui {
pub(crate) fn ed_hold(&mut self, id: u32, anchor: usize, caret: usize) {
let e = &mut self.m.edit;
if e.ed_id != id {
if e.ed_id != 0 {
e.ed_blur = e.ed_id;
}
e.ed_id = id;
e.ed_drag = false;
self.ud_reset();
}
let frame = self.frame;
let e = &mut self.m.edit;
if e.ed_blur == id {
e.ed_blur = 0;
}
e.ed_anchor = anchor;
e.ed_caret = caret;
e.ed_seen = frame;
e.ed_multi = true;
e.ed_prefx = -1;
}
pub(crate) fn ed_drop(&mut self, id: u32) {
if self.m.edit.ed_id == id {
self.m.edit.ed_id = 0;
self.ime_off();
}
}
}