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

Highlight selected text in input fields #10176

Merged
merged 2 commits into from Mar 26, 2016
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -709,13 +709,13 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>
return
}

let insertion_point = node.insertion_point();
let selection = node.selection();
let mut style = (*style).clone();
properties::modify_style_for_text(&mut style);

match text_content {
TextContent::Text(string) => {
let info = UnscannedTextFragmentInfo::new(string, insertion_point);
let info = UnscannedTextFragmentInfo::new(string, selection);
let specific_fragment_info = SpecificFragmentInfo::UnscannedText(info);
fragments.fragments.push_back(Fragment::from_opaque_node_and_style(
node.opaque(),
@@ -104,6 +104,10 @@ impl<'a> DisplayListBuildState<'a> {
/// The logical width of an insertion point: at the moment, a one-pixel-wide line.
const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(1 * AU_PER_PX);

// Colors for selected text. TODO (#8077): Use the ::selection pseudo-element to set these.
const SELECTION_FOREGROUND_COLOR: RGBA = RGBA { red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0 };
const SELECTION_BACKGROUND_COLOR: RGBA = RGBA { red: 1.0, green: 0.5, blue: 0.0, alpha: 1.0 };

// TODO(gw): The transforms spec says that perspective length must
// be positive. However, there is some confusion between the spec
// and browser implementations as to handling the case of 0 for the
@@ -922,6 +926,23 @@ impl FragmentDisplayListBuilding for Fragment {
}
_ => return,
};

// Draw a highlighted background if the text is selected.
//
// TODO: Allow non-text fragments to be selected too.
if scanned_text_fragment_info.selected() {
state.add_display_item(
DisplayItem::SolidColorClass(box SolidColorDisplayItem {
base: BaseDisplayItem::new(stacking_relative_border_box,
DisplayItemMetadata::new(self.node,
&*self.style,
Cursor::DefaultCursor),
&clip),
color: SELECTION_BACKGROUND_COLOR.to_gfx_color()
}), display_list_section);
}

// Draw a caret at the insertion point.
let insertion_point_index = match scanned_text_fragment_info.insertion_point {
Some(insertion_point_index) => insertion_point_index,
None => return,
@@ -1095,7 +1116,11 @@ impl FragmentDisplayListBuilding for Fragment {
//
// NB: According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front
// to back).
let text_color = self.style().get_color().color;
let text_color = if text_fragment.selected() {
SELECTION_FOREGROUND_COLOR
} else {
self.style().get_color().color
};
for text_shadow in self.style.get_effects().text_shadow.0.iter().rev() {
let offset = &Point2D::new(text_shadow.offset_x, text_shadow.offset_y);
let color = self.style().resolve_color(text_shadow.color);
@@ -655,8 +655,6 @@ pub struct ScannedTextFragmentInfo {
pub content_size: LogicalSize<Au>,

/// The position of the insertion point in characters, if any.
///
/// TODO(pcwalton): Make this a range.
pub insertion_point: Option<CharIndex>,

/// The range within the above text run that this represents.
@@ -667,29 +665,45 @@ pub struct ScannedTextFragmentInfo {
/// performing incremental reflow.
pub range_end_including_stripped_whitespace: CharIndex,

/// Whether a line break is required after this fragment if wrapping on newlines (e.g. if
/// `white-space: pre` is in effect).
pub requires_line_break_afterward_if_wrapping_on_newlines: bool,
pub flags: ScannedTextFlags,
}

bitflags! {
flags ScannedTextFlags: u8 {
/// Whether a line break is required after this fragment if wrapping on newlines (e.g. if
/// `white-space: pre` is in effect).
const REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES = 0x01,

/// Is this fragment selected?
const SELECTED = 0x02,
}
}

impl ScannedTextFragmentInfo {
/// Creates the information specific to a scanned text fragment from a range and a text run.
pub fn new(run: Arc<TextRun>,
range: Range<CharIndex>,
content_size: LogicalSize<Au>,
insertion_point: &Option<CharIndex>,
requires_line_break_afterward_if_wrapping_on_newlines: bool)
insertion_point: Option<CharIndex>,
flags: ScannedTextFlags)
-> ScannedTextFragmentInfo {
ScannedTextFragmentInfo {
run: run,
range: range,
insertion_point: *insertion_point,
insertion_point: insertion_point,
content_size: content_size,
range_end_including_stripped_whitespace: range.end(),
requires_line_break_afterward_if_wrapping_on_newlines:
requires_line_break_afterward_if_wrapping_on_newlines,
flags: flags,
}
}

pub fn requires_line_break_afterward_if_wrapping_on_newlines(&self) -> bool {
self.flags.contains(REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES)
}

pub fn selected(&self) -> bool {
self.flags.contains(SELECTED)
}
}

/// Describes how to split a fragment. This is used during line breaking as part of the return
@@ -737,19 +751,17 @@ pub struct UnscannedTextFragmentInfo {
/// The text inside the fragment.
pub text: Box<str>,

/// The position of the insertion point, if any.
///
/// TODO(pcwalton): Make this a range.
pub insertion_point: Option<CharIndex>,
/// The selected text range. An empty range represents the insertion point.
pub selection: Option<Range<CharIndex>>,
}

impl UnscannedTextFragmentInfo {
/// Creates a new instance of `UnscannedTextFragmentInfo` from the given text.
#[inline]
pub fn new(text: String, insertion_point: Option<CharIndex>) -> UnscannedTextFragmentInfo {
pub fn new(text: String, selection: Option<Range<CharIndex>>) -> UnscannedTextFragmentInfo {
UnscannedTextFragmentInfo {
text: text.into_boxed_str(),
insertion_point: insertion_point,
selection: selection,
}
}
}
@@ -865,15 +877,17 @@ impl Fragment {
let size = LogicalSize::new(self.style.writing_mode,
split.inline_size,
self.border_box.size.block);
let requires_line_break_afterward_if_wrapping_on_newlines =
self.requires_line_break_afterward_if_wrapping_on_newlines();
let flags = match self.specific {
SpecificFragmentInfo::ScannedText(ref info) => info.flags,
_ => ScannedTextFlags::empty()
};
// FIXME(pcwalton): This should modify the insertion point as necessary.
let info = box ScannedTextFragmentInfo::new(
text_run,
split.range,
size,
&None,
requires_line_break_afterward_if_wrapping_on_newlines);
None,
flags);
self.transform(size, SpecificFragmentInfo::ScannedText(info))
}

@@ -1663,9 +1677,9 @@ impl Fragment {
this_info.range.extend_to(other_info.range_end_including_stripped_whitespace);
this_info.content_size.inline =
this_info.run.metrics_for_range(&this_info.range).advance_width;
this_info.requires_line_break_afterward_if_wrapping_on_newlines =
this_info.requires_line_break_afterward_if_wrapping_on_newlines ||
other_info.requires_line_break_afterward_if_wrapping_on_newlines;
if other_info.requires_line_break_afterward_if_wrapping_on_newlines() {
this_info.flags.insert(REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES);
}
self.border_padding.inline_end = next_fragment.border_padding.inline_end;
self.border_box.size.inline = this_info.content_size.inline +
self.border_padding.inline_start_end();
@@ -2229,7 +2243,7 @@ impl Fragment {
pub fn requires_line_break_afterward_if_wrapping_on_newlines(&self) -> bool {
match self.specific {
SpecificFragmentInfo::ScannedText(ref scanned_text) => {
scanned_text.requires_line_break_afterward_if_wrapping_on_newlines
scanned_text.requires_line_break_afterward_if_wrapping_on_newlines()
}
_ => false,
}
@@ -354,6 +354,7 @@ impl LineBreaker {
let need_to_merge = match (&mut result.specific, &candidate.specific) {
(&mut SpecificFragmentInfo::ScannedText(ref mut result_info),
&SpecificFragmentInfo::ScannedText(ref candidate_info)) => {
result_info.selected() == candidate_info.selected() &&
util::arc_ptr_eq(&result_info.run, &candidate_info.run) &&
inline_contexts_are_equal(&result.inline_context,
&candidate.inline_context)
@@ -7,7 +7,8 @@
#![deny(unsafe_code)]

use app_units::Au;
use fragment::{Fragment, ScannedTextFragmentInfo, SpecificFragmentInfo, UnscannedTextFragmentInfo};
use fragment::{Fragment, REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES, ScannedTextFlags};
use fragment::{ScannedTextFragmentInfo, SELECTED, SpecificFragmentInfo, UnscannedTextFragmentInfo};
use gfx::font::{DISABLE_KERNING_SHAPING_FLAG, FontMetrics, IGNORE_LIGATURES_SHAPING_FLAG};
use gfx::font::{RTL_FLAG, RunMetrics, ShapingFlags, ShapingOptions};
use gfx::font_context::FontContext;
@@ -172,17 +173,21 @@ impl TextRunScanner {
for (fragment_index, in_fragment) in self.clump.iter().enumerate() {
let mut mapping = RunMapping::new(&run_info_list[..], &run_info, fragment_index);
let text;
let insertion_point;
let selection;
match in_fragment.specific {
SpecificFragmentInfo::UnscannedText(ref text_fragment_info) => {
text = &text_fragment_info.text;
insertion_point = text_fragment_info.insertion_point;
selection = text_fragment_info.selection;
}
_ => panic!("Expected an unscanned text fragment!"),
};
let insertion_point = match selection {
Some(range) if range.is_empty() => Some(range.begin()),
_ => None
};

let (mut start_position, mut end_position) = (0, 0);
for character in text.chars() {
for (char_index, character) in text.chars().enumerate() {
// Search for the first font in this font group that contains a glyph for this
// character.
let mut font_index = 0;
@@ -213,11 +218,18 @@ impl TextRunScanner {
run_info.script = script;
}

let selected = match selection {
Some(range) => range.contains(CharIndex(char_index as isize)),
None => false
};

// Now, if necessary, flush the mapping we were building up.
if run_info.font_index != font_index ||
run_info.bidi_level != bidi_level ||
!compatible_script
{
let flush_run = run_info.font_index != font_index ||
run_info.bidi_level != bidi_level ||
!compatible_script;
let flush_mapping = flush_run || mapping.selected != selected;

if flush_mapping {
if end_position > start_position {
mapping.flush(&mut mappings,
&mut run_info,
@@ -230,15 +242,18 @@ impl TextRunScanner {
end_position);
}
if run_info.text.len() > 0 {
run_info_list.push(run_info);
run_info = RunInfo::new();
if flush_run {
run_info_list.push(run_info);
run_info = RunInfo::new();
}
mapping = RunMapping::new(&run_info_list[..],
&run_info,
fragment_index);
}
run_info.font_index = font_index;
run_info.bidi_level = bidi_level;
run_info.script = script;
mapping.selected = selected;
}

// Consume this character.
@@ -330,12 +345,20 @@ impl TextRunScanner {
}

let text_size = old_fragment.border_box.size;

let mut flags = ScannedTextFlags::empty();
if mapping.selected {
flags.insert(SELECTED);
}
if requires_line_break_afterward_if_wrapping_on_newlines {
flags.insert(REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES);
}
let mut new_text_fragment_info = box ScannedTextFragmentInfo::new(
scanned_run.run,
mapping.char_range,
text_size,
&scanned_run.insertion_point,
requires_line_break_afterward_if_wrapping_on_newlines);
scanned_run.insertion_point,
flags);

let new_metrics = new_text_fragment_info.run.metrics_for_range(&mapping.char_range);
let writing_mode = old_fragment.style.writing_mode;
@@ -408,7 +431,7 @@ fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragm
let new_fragment = {
let mut first_fragment = fragments.front_mut().unwrap();
let string_before;
let insertion_point_before;
let selection_before;
{
if !first_fragment.white_space().preserve_newlines() {
return;
@@ -433,21 +456,28 @@ fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragm
unscanned_text_fragment_info.text =
unscanned_text_fragment_info.text[(position + 1)..].to_owned().into_boxed_str();
let offset = CharIndex(string_before.char_indices().count() as isize);
match unscanned_text_fragment_info.insertion_point {
Some(insertion_point) if insertion_point >= offset => {
insertion_point_before = None;
unscanned_text_fragment_info.insertion_point = Some(insertion_point - offset);
match unscanned_text_fragment_info.selection {
Some(ref mut selection) if selection.begin() >= offset => {
// Selection is entirely in the second fragment.
selection_before = None;
selection.shift_by(-offset);
}
Some(ref mut selection) if selection.end() > offset => {
// Selection is split across two fragments.
selection_before = Some(Range::new(selection.begin(), offset));
*selection = Range::new(CharIndex(0), selection.end() - offset);
}
Some(_) | None => {
insertion_point_before = unscanned_text_fragment_info.insertion_point;
unscanned_text_fragment_info.insertion_point = None;
_ => {
// Selection is entirely in the first fragment.
selection_before = unscanned_text_fragment_info.selection;
unscanned_text_fragment_info.selection = None;
}
};
}
first_fragment.transform(first_fragment.border_box.size,
SpecificFragmentInfo::UnscannedText(
UnscannedTextFragmentInfo::new(string_before,
insertion_point_before)))
selection_before)))
};

fragments.push_front(new_fragment);
@@ -494,6 +524,8 @@ struct RunMapping {
old_fragment_index: usize,
/// The index of the text run we're going to create.
text_run_index: usize,
/// Is the text in this fragment selected?
selected: bool,
}

impl RunMapping {
@@ -508,6 +540,7 @@ impl RunMapping {
byte_range: Range::new(0, 0),
old_fragment_index: fragment_index,
text_run_index: run_info_list.len(),
selected: false,
}
}

ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.