Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

text: Fix selection background rendering #18437

Merged
merged 7 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 115 additions & 36 deletions core/src/display_object/edit_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,25 @@ impl<'gc> EditText<'gc> {
}
}

/// Returns the selection, but takes into account whether the selection should be rendered.
fn visible_selection(self, edit_text: &EditTextData<'gc>) -> Option<TextSelection> {
let selection = edit_text.selection?;
#[allow(clippy::collapsible_else_if)]
if selection.is_caret() {
if self.has_focus() && !edit_text.flags.contains(EditTextFlag::READ_ONLY) {
Some(selection)
} else {
None
}
} else {
if self.has_focus() || self.always_show_selection() {
Some(selection)
} else {
None
}
}
}

fn render_debug_boxes(
self,
context: &mut RenderContext<'_, 'gc>,
Expand Down Expand Up @@ -964,6 +983,100 @@ impl<'gc> EditText<'gc> {
}
}

/// Render lines according to the given procedure.
///
/// This skips invisible lines.
fn render_lines<F>(self, context: &mut RenderContext<'_, 'gc>, layout: &Layout<'gc>, f: F)
where
F: Fn(&mut RenderContext<'_, 'gc>, &LayoutLine<'gc>),
{
// Skip lines that are off-screen.
let lines_to_skip = self.scroll().saturating_sub(1);
for line in layout.lines().iter().skip(lines_to_skip) {
f(context, line);
}
}

/// Render the visible text along with selection and the caret.
fn render_text(self, context: &mut RenderContext<'_, 'gc>, edit_text: &EditTextData<'gc>) {
self.render_selection_background(context, edit_text);
self.render_lines(context, &edit_text.layout, |context, line| {
self.render_layout_line(context, line);
});
}

/// Render the black selection background.
fn render_selection_background(
self,
context: &mut RenderContext<'_, 'gc>,
edit_text: &EditTextData<'gc>,
) {
let Some(selection) = self.visible_selection(edit_text) else {
return;
};
if selection.is_caret() {
return;
}

let (start, end) = (selection.start(), selection.end());

self.render_lines(context, &edit_text.layout, |context, line| {
self.render_selection_background_for_line(context, line, start, end)
});
}

fn render_selection_background_for_line(
self,
context: &mut RenderContext<'_, 'gc>,
line: &LayoutLine<'gc>,
start: usize,
end: usize,
) {
let local_start = start.clamp(line.start(), line.end());
let local_end = end.clamp(line.start(), line.end());

if local_start >= local_end {
// No selection in this line
return;
}

let line_bounds = line.bounds();

// If the selection ends within this line, the background
// is not drawn over leading.
let leading = if local_end == end {
Twips::ZERO
} else {
line.leading()
};

let x_start = line
.char_x_bounds(local_start)
.map(|b| b.0)
.unwrap_or_else(|| line_bounds.offset_x());
let x_end = line
.char_x_bounds(local_end - 1)
.map(|b| b.1)
.unwrap_or_else(|| line_bounds.extent_x());

let width = x_end - x_start;
let height = line_bounds.height() + leading;

let color = if self.has_focus() {
Color::BLACK
} else {
Color::GRAY
};
let selection_box = context.transform_stack.transform().matrix
* Matrix::create_box(
width.to_pixels() as f32,
height.to_pixels() as f32,
x_start,
line_bounds.origin().y(),
);
context.commands.draw_rect(color, selection_box);
}

fn render_layout_line(self, context: &mut RenderContext<'_, 'gc>, line: &LayoutLine<'gc>) {
for layout_box in line.boxes_iter() {
self.render_layout_box(context, layout_box);
Expand Down Expand Up @@ -994,15 +1107,7 @@ impl<'gc> EditText<'gc> {
..Default::default()
});

let focused = self.has_focus();
let visible_selection = if focused {
edit_text.selection
} else if self.always_show_selection() {
// Caret is not shown even if alwaysShowSelection is true
edit_text.selection.filter(|sel| !sel.is_caret())
} else {
None
};
let visible_selection = self.visible_selection(&edit_text);

let caret = if let LayoutContent::Text { start, end, .. } = &lbox.content() {
if let Some(visible_selection) = visible_selection {
Expand Down Expand Up @@ -1050,9 +1155,6 @@ impl<'gc> EditText<'gc> {
if let Some(glyph_shape_handle) = glyph.shape_handle(context.renderer) {
// If it's highlighted, override the color.
if matches!(visible_selection, Some(visible_selection) if visible_selection.contains(start + pos)) {
// Draw selection rect
self.render_selection(context, x, advance, caret_height, focused);

// Set text color to white
context.transform_stack.push(&Transform {
matrix: transform.matrix,
Expand Down Expand Up @@ -1093,25 +1195,6 @@ impl<'gc> EditText<'gc> {
context.transform_stack.pop();
}

fn render_selection(
self,
context: &mut RenderContext<'_, 'gc>,
x: Twips,
width: Twips,
height: Twips,
focused: bool,
) {
let color = if focused { Color::BLACK } else { Color::GRAY };
let selection_box = context.transform_stack.transform().matrix
* Matrix::create_box(
width.to_pixels() as f32,
height.to_pixels() as f32,
x,
Twips::ZERO,
);
context.commands.draw_rect(color, selection_box);
}

fn render_caret(
self,
context: &mut RenderContext<'_, 'gc>,
Expand Down Expand Up @@ -2379,11 +2462,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
..Default::default()
});

// Skip lines that are off-screen.
let lines_to_skip = self.scroll().saturating_sub(1);
for line in edit_text.layout.lines().iter().skip(lines_to_skip) {
self.render_layout_line(context, line);
}
self.render_text(context, &edit_text);

self.render_debug_boxes(
context,
Expand Down
18 changes: 11 additions & 7 deletions core/src/html/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1104,18 +1104,22 @@ impl<'gc> LayoutLine<'gc> {
result.ok()
}

/// Returns char bounds of the given char relative to the whole layout.
pub fn char_bounds(&self, position: usize) -> Option<Rectangle<Twips>> {
/// Returns x-axis char bounds of the given char relative to the whole layout.
pub fn char_x_bounds(&self, position: usize) -> Option<(Twips, Twips)> {
let box_index = self.find_box_index_by_position(position)?;
let layout_box = self.boxes.get(box_index)?;

let line_bounds = self.bounds();
let origin_x = layout_box.bounds().origin().x();
let x_bounds = layout_box.char_x_bounds(position)?;
let (start, end) = layout_box.char_x_bounds(position)?;
Some((origin_x + start, origin_x + end))
}

/// Returns char bounds of the given char relative to the whole layout.
pub fn char_bounds(&self, position: usize) -> Option<Rectangle<Twips>> {
let (x_min, x_max) = self.char_x_bounds(position)?;
let line_bounds = self.bounds();
Some(Rectangle {
x_min: origin_x + x_bounds.0,
x_max: origin_x + x_bounds.1,
x_min,
x_max,
y_min: line_bounds.offset_y(),
y_max: line_bounds.extent_y(),
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package {
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFormat;

[SWF(width="250", height="60")]
public class Test extends Sprite {
[Embed(source="TestFont.ttf", fontName="TestFont", embedAsCFF="false", unicodeRange="U+0061-U+0064")]
private var testFont:Class;

public function Test() {
stage.scaleMode = "noScale";
newTextField();
}

private function newTextField(): void {
var text:TextField = new TextField();
text.border = true;
text.width = 247;
text.x = 1;
text.y = 1;
text.multiline = true;
text.height = 57;
text.embedFonts = true;
text.selectable = true;
text.textColor = 0xFF00FF;
text.background = true;
text.backgroundColor = 0x00FFFF;
var tf = new TextFormat();
tf.font = "TestFont";
tf.size = 10;
text.defaultTextFormat = tf;
text.htmlText = "ac<font size='+4'>ac<font size='+4'>ac<font size='+4'>ac<font size='+4'>ac<font size='+4'>ac</font></font></font></font></font>";
stage.focus = text;
text.setSelection(1, 7);
addChild(text);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
SplineFontDB: 3.2
FontName: TestFont
FullName: TestFont
FamilyName: TestFont
Weight: Regular
Copyright: Copyright (c) 2024, Kamil Jarosz
UComments: "2024-7-24: Created with FontForge (http://fontforge.org)"
Version: 001.000
ItalicAngle: 0
UnderlinePosition: -97
UnderlineWidth: 48
Ascent: 800
Descent: 200
InvalidEm: 0
LayerCount: 2
Layer: 0 0 "Back" 1
Layer: 1 0 "Fore" 0
XUID: [1021 253 198287149 6396829]
StyleMap: 0x0000
FSType: 0
OS2Version: 0
OS2_WeightWidthSlopeOnly: 0
OS2_UseTypoMetrics: 1
CreationTime: 1721856925
ModificationTime: 1730383021
PfmFamily: 17
TTFWeight: 400
TTFWidth: 5
LineGap: 100
VLineGap: 0
OS2TypoAscent: 0
OS2TypoAOffset: 1
OS2TypoDescent: 0
OS2TypoDOffset: 1
OS2TypoLinegap: 100
OS2WinAscent: 0
OS2WinAOffset: 1
OS2WinDescent: 0
OS2WinDOffset: 1
HheadAscent: 0
HheadAOffset: 1
HheadDescent: 0
HheadDOffset: 1
OS2Vendor: 'PfEd'
MarkAttachClasses: 1
DEI: 91125
Encoding: ISO8859-1
UnicodeInterp: none
NameList: AGL For New Fonts
DisplaySize: -48
AntiAlias: 1
FitToEm: 0
WinInfo: 0 30 10
BeginPrivate: 0
EndPrivate
BeginChars: 256 4

StartChar: a
Encoding: 97 97 0
Width: 800
Flags: HW
LayerCount: 2
Fore
SplineSet
100 700 m 1
700 700 l 5
700 0 l 5
100 0 l 1
100 700 l 1
EndSplineSet
EndChar

StartChar: b
Encoding: 98 98 1
Width: 100
Flags: HW
LayerCount: 2
Fore
SplineSet
0 800 m 1
100 800 l 1
100 0 l 1
0 0 l 1
0 800 l 1
EndSplineSet
EndChar

StartChar: c
Encoding: 99 99 2
Width: 800
Flags: HWO
LayerCount: 2
Fore
SplineSet
100 0 m 1
700 0 l 1
700 -150 l 1
100 -150 l 1
100 0 l 1
EndSplineSet
EndChar

StartChar: d
Encoding: 100 100 3
Width: 100
Flags: HW
LayerCount: 2
Fore
SplineSet
0 0 m 1
100 0 l 1
100 -200 l 1
0 -200 l 1
0 0 l 1
EndSplineSet
EndChar
EndChars
EndSplineFont
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
num_ticks = 1

[image_comparisons."output"]
tolerance = 128

[player_options]
with_renderer = { optional = false, sample_count = 4 }
Loading