///|
fn rich_text_block_source_offset_to_visual(
block : RichTextBlock,
source_offset : Int,
content : RichTextSourceRange,
) -> Int {
if source_offset < content.start {
0
} else if source_offset > content.end {
rich_text_block_visual_length(block)
} else {
let prefix_length = text_length(block.prefix)
match rich_text_block_run_visual_offset_from_source(block, source_offset) {
Some(offset) => prefix_length + offset
None =>
clamp_int(
prefix_length + source_offset - content.start,
0,
rich_text_block_visual_length(block),
)
}
}
}
///|
fn rich_text_block_run_visual_offset_from_source(
block : RichTextBlock,
source_offset : Int,
) -> Int? {
let mut visual = 0
for run in block.runs {
let length = text_length(run.text)
let visual_length = text_length(rich_text_run_visual_text(run))
match run.source_range {
Some(range) =>
if source_offset <= range.end {
return Some(
visual +
rich_text_run_visual_offset_from_source_offset(
source_offset - range.start,
length,
visual_length,
),
)
}
None => return None
}
visual = visual + visual_length
}
Some(visual)
}
///|
fn rich_text_table_column_count(table : RichTextTable) -> Int {
let mut count = 0
for row in table.rows {
count = rich_text_max_int(count, row.length())
}
count
}
///|
pub fn rich_text_editor_content_rect(frame : Rect) -> Rect {
frame.inset(Insets::symmetric(horizontal=14.0, vertical=12.0))
}
///|
pub fn rich_text_document_caret_rect_at_source(
document : RichTextDocument,
source_caret : Int,
content : Rect,
base_font : FontSpec,
text_system? : TextSystem = TextSystem::fallback(),
) -> Rect? {
rich_text_document_caret_rect(
document, source_caret, content, base_font, text_system,
)
}
///|
fn rich_text_editor_caret_at_point(
data : RichTextEditorViewSpec,
point : Point,
frame : Rect,
text_system : TextSystem,
) -> Int {
let text = data.value.get()
let document = (data.format)(text)
let content = rich_text_editor_content_rect(frame)
rich_text_document_source_offset_at_point(
document,
point,
content,
data.font,
text_system,
).unwrap_or(text_length(text))
}
///|
fn rich_text_editor_prefix_source_offset_at_point(
data : RichTextEditorViewSpec,
point : Point,
frame : Rect,
text_system : TextSystem,
) -> Int? {
let text = data.value.get()
let document = (data.format)(text)
let content = rich_text_editor_content_rect(frame)
rich_text_document_prefix_source_offset_at_point(
document,
point,
content,
data.font,
text_system,
)
}
///|
fn rich_text_block_table_source_offset_at_point(
block : RichTextBlock,
point : Point,
frame : Rect,
) -> Int? {
match block.table {
Some(table) => {
let row_count = table.rows.length()
let column_count = rich_text_table_column_count(table)
if row_count == 0 || column_count == 0 || table.row_height <= 0.0 {
return None
}
let row_index = clamp_int(
((point.y - frame.origin.y) / table.row_height).floor().to_int(),
0,
row_count - 1,
)
let column_width = frame.size.width / column_count.to_double()
let column_index = clamp_int(
((point.x - frame.origin.x) / column_width).floor().to_int(),
0,
column_count - 1,
)
let row = table.rows[row_index]
if column_index >= row.length() {
return None
}
match row[column_index].source_range {
Some(range) => {
let x = clamp_double(
point.x,
frame.origin.x + column_width * column_index.to_double(),
frame.origin.x + column_width * (column_index + 1).to_double(),
)
let relative = if column_width <= 0.0 {
0.0
} else {
(x - (frame.origin.x + column_width * column_index.to_double())) /
column_width
}
Some(
clamp_int(
range.start +
(relative * (range.end - range.start).to_double())
.round()
.to_int(),
range.start,
range.end,
),
)
}
None => None
}
}
None => None
}
}
///|
fn rich_text_block_source_offset_at_x(
block : RichTextBlock,
x : Double,
frame : Rect,
base_font : FontSpec,
text_system : TextSystem,
) -> Int {
let visual = rich_text_block_visual_offset_at_x(
block, x, frame, base_font, text_system,
)
rich_text_block_source_offset_from_visual(block, visual)
}
///|
fn rich_text_block_source_offset_from_visual(
block : RichTextBlock,
visual_offset : Int,
) -> Int {
match block.content_range {
Some(content) => {
let prefix_length = text_length(block.prefix)
if visual_offset <= prefix_length {
content.start
} else {
match
rich_text_block_run_source_offset_from_visual(
block,
visual_offset - prefix_length,
) {
Some(offset) => offset
None =>
clamp_int(
content.start + visual_offset - prefix_length,
content.start,
content.end,
)
}
}
}
None => visual_offset
}
}
///|
fn rich_text_block_run_source_offset_from_visual(
block : RichTextBlock,
visual_offset : Int,
) -> Int? {
let mut remaining = visual_offset
for run in block.runs {
let length = text_length(run.text)
let visual_length = text_length(rich_text_run_visual_text(run))
if remaining <= visual_length {
match run.source_range {
Some(range) =>
return Some(
clamp_int(
range.start +
rich_text_run_source_offset_from_visual_offset(
remaining, visual_length, length,
),
range.start,
range.end,
),
)
None => return None
}
}
remaining = remaining - visual_length
}
match block.runs.last() {
Some(run) =>
match run.source_range {
Some(range) => Some(range.end)
None => None
}
None => None
}
}
///|
fn rich_text_document_has_source_ranges(document : RichTextDocument) -> Bool {
document.blocks.any(block => block.content_range is Some(_))
}
///|
fn rich_text_caret_before_block(
source_caret : Int,
block : RichTextBlock,
) -> Bool {
match block.source_range {
Some(range) => source_caret < range.start
None => false
}
}
///|
fn rich_text_block_visual_offset(
block : RichTextBlock,
source_caret : Int,
) -> Int? {
match (block.source_range, block.content_range) {
(Some(source), Some(content)) =>
if source_caret < source.start || source_caret > source.end {
None
} else if source_caret < content.start {
Some(0)
} else if source_caret > content.end {
Some(rich_text_block_visual_length(block))
} else {
Some(
rich_text_block_source_offset_to_visual(block, source_caret, content),
)
}
(_, Some(content)) =>
if source_caret < content.start || source_caret > content.end {
None
} else {
Some(
rich_text_block_source_offset_to_visual(block, source_caret, content),
)
}
_ => None
}
}
///|
fn rich_text_block_visual_length(block : RichTextBlock) -> Int {
let mut length = text_length(block.prefix)
for run in block.runs {
length = length + text_length(rich_text_run_visual_text(run))
}
length
}
///|
fn rich_text_run_visual_text(run : RichTextRun) -> String {
run.visual_text.unwrap_or(run.text)
}
///|
fn rich_text_run_visual_offset_from_source_offset(
source_offset : Int,
source_length : Int,
visual_length : Int,
) -> Int {
if source_length <= 0 || visual_length <= 0 {
0
} else if source_offset <= 0 {
0
} else if source_offset >= source_length {
visual_length
} else {
(source_offset.to_double() *
visual_length.to_double() /
source_length.to_double())
.round()
.to_int()
}
}
///|
fn rich_text_run_source_offset_from_visual_offset(
visual_offset : Int,
visual_length : Int,
source_length : Int,
) -> Int {
if visual_length <= 0 || source_length <= 0 {
0
} else if visual_offset <= 0 {
0
} else if visual_offset >= visual_length {
source_length
} else {
(visual_offset.to_double() *
source_length.to_double() /
visual_length.to_double())
.round()
.to_int()
}
}
///|
fn rich_text_prefix(text : String, length : Int) -> String {
let chars = text.to_array()
let end = clamp_int(length, 0, chars.length())
String::from_array(chars[0:end])
}
///|
fn rich_text_cursor_rect_at(x : Double, frame : Rect, font : FontSpec) -> Rect {
Rect::new(
x~,
y=frame.origin.y + 4.0,
width=1.5,
height=max_double(10.0, font.size * 1.55 - 8.0),
)
}
///|
fn rich_text_max_int(a : Int, b : Int) -> Int {
if a > b {
a
} else {
b
}
}
///|
fn rich_text_document_caret_rect(
document : RichTextDocument,
source_caret : Int,
content : Rect,
base_font : FontSpec,
text_system : TextSystem,
) -> Rect? {
if !rich_text_document_has_source_ranges(document) {
return None
}
let mut y = content.origin.y
let mut last_rect = Rect::new(
x=content.origin.x,
y=content.origin.y + 4.0,
width=1.5,
height=max_double(10.0, base_font.size * 1.55 - 8.0),
)
for block in document.blocks {
let line_height = rich_text_block_line_height(block, base_font)
let block_height = line_height + block.inset.top + block.inset.bottom
let frame = Rect::new(
x=content.origin.x + block.inset.left,
y=y + block.inset.top,
width=max_double(
0.0,
content.size.width - block.inset.left - block.inset.right,
),
height=line_height,
)
if rich_text_caret_before_block(source_caret, block) {
return Some(
rich_text_block_caret_rect(block, 0, frame, base_font, text_system),
)
}
match rich_text_block_visual_offset(block, source_caret) {
Some(visual_offset) =>
return Some(
rich_text_block_caret_rect(
block, visual_offset, frame, base_font, text_system,
),
)
None => ()
}
last_rect = rich_text_block_caret_rect(
block,
rich_text_block_visual_length(block),
frame,
base_font,
text_system,
)
y = y + block_height
}
Some(last_rect)
}
///|
fn rich_text_document_prefix_source_offset_at_point(
document : RichTextDocument,
point : Point,
content : Rect,
base_font : FontSpec,
text_system : TextSystem,
) -> Int? {
if !rich_text_document_has_source_ranges(document) {
return None
}
let mut y = content.origin.y
for block in document.blocks {
let line_height = rich_text_block_line_height(block, base_font)
let block_height = line_height + block.inset.top + block.inset.bottom
let frame = Rect::new(
x=content.origin.x + block.inset.left,
y=y + block.inset.top,
width=max_double(
0.0,
content.size.width - block.inset.left - block.inset.right,
),
height=line_height,
)
if point.y < y + block_height {
return rich_text_block_prefix_source_offset_at_point(
block, point, frame, base_font, text_system,
)
}
y = y + block_height
}
None
}
///|
fn rich_text_block_prefix_source_offset_at_point(
block : RichTextBlock,
point : Point,
frame : Rect,
base_font : FontSpec,
text_system : TextSystem,
) -> Int? {
if block.prefix == "" {
return None
}
let font = rich_text_block_font(block, base_font)
let width = rich_text_width(block.prefix, font, text_system)
if point.x >= frame.origin.x && point.x <= frame.origin.x + width {
match block.content_range {
Some(content) => Some(content.start)
None => None
}
} else {
None
}
}
///|
fn rich_text_document_source_offset_at_point(
document : RichTextDocument,
point : Point,
content : Rect,
base_font : FontSpec,
text_system : TextSystem,
) -> Int? {
if !rich_text_document_has_source_ranges(document) {
return None
}
let mut y = content.origin.y
let mut last_offset : Int? = None
for block in document.blocks {
let line_height = rich_text_block_line_height(block, base_font)
let block_height = line_height + block.inset.top + block.inset.bottom
let frame = Rect::new(
x=content.origin.x + block.inset.left,
y=y + block.inset.top,
width=max_double(
0.0,
content.size.width - block.inset.left - block.inset.right,
),
height=line_height,
)
let offset = rich_text_block_source_offset_at_x(
block,
point.x,
frame,
base_font,
text_system,
)
last_offset = Some(offset)
if point.y < y + block_height {
match rich_text_block_table_source_offset_at_point(block, point, frame) {
Some(offset) => return Some(offset)
None => ()
}
return Some(offset)
}
y = y + block_height
}
last_offset
}
///|
fn rich_text_block_visual_offset_at_x(
block : RichTextBlock,
x : Double,
frame : Rect,
base_font : FontSpec,
text_system : TextSystem,
) -> Int {
let font = rich_text_block_font(block, base_font)
let mut origin = frame.origin.x
let mut visual_offset = 0
if block.prefix != "" {
let prefix_length = text_length(block.prefix)
let hit = rich_text_run_visual_offset_at_x(
block.prefix,
font,
x,
origin,
text_system,
)
if hit < prefix_length ||
x <= origin + rich_text_width(block.prefix, font, text_system) {
return hit
}
visual_offset = visual_offset + prefix_length
origin = origin + rich_text_width(block.prefix, font, text_system)
}
for run in block.runs {
let run_font = run.font.unwrap_or(font)
let text = rich_text_run_visual_text(run)
let run_length = text_length(text)
let run_width = rich_text_run_width(run, run_font, text_system)
if x <= origin + run_width {
return visual_offset +
rich_text_run_visual_offset_at_x(
text,
run_font,
x,
origin,
text_system,
width=rich_text_run_hit_width(run, run_width),
)
}
visual_offset = visual_offset + run_length
origin = origin + run_width
}
rich_text_block_visual_length(block)
}
///|
fn rich_text_run_visual_offset_at_x(
text : String,
font : FontSpec,
x : Double,
origin_x : Double,
text_system : TextSystem,
width? : Double? = None,
) -> Int {
let chars = text.to_array()
if x <= origin_x {
return 0
}
match width {
Some(width) =>
if chars.length() <= 1 {
if x < origin_x + width / 2.0 {
return 0
} else {
return chars.length()
}
} else {
let relative = clamp_double((x - origin_x) / width, 0.0, 1.0)
return (relative * chars.length().to_double()).round().to_int()
}
None => ()
}
for index in 0.. Rect {
let font = rich_text_block_font(block, base_font)
let mut remaining = clamp_int(
visual_offset,
0,
rich_text_block_visual_length(block),
)
let mut x = frame.origin.x
if block.prefix != "" {
let prefix_length = text_length(block.prefix)
if remaining <= prefix_length {
let prefix = rich_text_prefix(block.prefix, remaining)
x = x + rich_text_width(prefix, font, text_system)
return rich_text_cursor_rect_at(x, frame, font)
}
remaining = remaining - prefix_length
x = x + rich_text_width(block.prefix, font, text_system)
}
for run in block.runs {
let run_font = run.font.unwrap_or(font)
let text = rich_text_run_visual_text(run)
let run_length = text_length(text)
if remaining <= run_length {
x = x + rich_text_run_prefix_width(run, remaining, run_font, text_system)
return rich_text_cursor_rect_at(x, frame, run_font)
}
remaining = remaining - run_length
x = x + rich_text_run_width(run, run_font, text_system)
}
rich_text_cursor_rect_at(x, frame, font)
}
///|
fn rich_text_run_prefix_width(
run : RichTextRun,
visual_offset : Int,
font : FontSpec,
text_system : TextSystem,
) -> Double {
let text = rich_text_run_visual_text(run)
let length = text_length(text)
if length <= 0 || visual_offset <= 0 {
0.0
} else if visual_offset >= length {
rich_text_run_width(run, font, text_system)
} else {
match run.image_source {
Some(_) =>
rich_text_run_width(run, font, text_system) *
visual_offset.to_double() /
length.to_double()
None =>
rich_text_width(
rich_text_prefix(text, visual_offset),
font,
text_system,
)
}
}
}
///|
fn rich_text_caret_rect(
text : String,
caret : Int,
content : Rect,
font : FontSpec,
text_system : TextSystem,
) -> Rect {
let chars = text.to_array()
let caret = clamp_int(caret, 0, chars.length())
let mut line = 0
let mut column_start = 0
for index in 0..