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

Position insertion point in input field with mouse #14291

Merged
merged 1 commit into from Jan 11, 2017
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
144 changes: 101 additions & 43 deletions components/gfx/display_list/mod.rs
Expand Up @@ -48,10 +48,74 @@ pub struct DisplayList {
}

impl DisplayList {
// Returns the text index within a node for the point of interest.
pub fn text_index(&self,
node: OpaqueNode,
client_point: &Point2D<Au>,
scroll_offsets: &ScrollOffsetMap)
-> Option<usize> {
let mut result = Vec::new();
let mut translated_point = client_point.clone();
let mut traversal = DisplayListTraversal::new(self);
self.text_index_contents(node,
&mut traversal,
&mut translated_point,
client_point,
scroll_offsets,
&mut result);
result.pop()
}

pub fn text_index_contents<'a>(&self,
node: OpaqueNode,
traversal: &mut DisplayListTraversal<'a>,
translated_point: &mut Point2D<Au>,
client_point: &Point2D<Au>,
scroll_offsets: &ScrollOffsetMap,
result: &mut Vec<usize>) {
while let Some(item) = traversal.next() {
match item {
&DisplayItem::PushStackingContext(ref stacking_context_item) => {
DisplayList::translate_point(&stacking_context_item.stacking_context,
translated_point,
client_point);
self.text_index_contents(node,
traversal,
translated_point,
client_point,
scroll_offsets,
result);
}
&DisplayItem::PushScrollRoot(ref item) => {
DisplayList::scroll_root(&item.scroll_root,
translated_point,
scroll_offsets);
self.text_index_contents(node,
traversal,
translated_point,
client_point,
scroll_offsets,
result);

},
&DisplayItem::PopStackingContext(_) => return,
&DisplayItem::Text(ref text) => {
let base = item.base();
if base.metadata.node == node {
let offset = *translated_point - text.baseline_origin;
let index = text.text_run.range_index_of_advance(&text.range, offset.x);
result.push(index);
}
},
_ => {},
}
}
}

// Return all nodes containing the point of interest, bottommost first, and
// respecting the `pointer-events` CSS property.
pub fn hit_test(&self,
translated_point: &Point2D<Au>,
translated_point: &mut Point2D<Au>,
client_point: &Point2D<Au>,
scroll_offsets: &ScrollOffsetMap)
-> Vec<DisplayItemMetadata> {
Expand All @@ -67,27 +131,31 @@ impl DisplayList {

pub fn hit_test_contents<'a>(&self,
traversal: &mut DisplayListTraversal<'a>,
translated_point: &Point2D<Au>,
translated_point: &mut Point2D<Au>,
client_point: &Point2D<Au>,
scroll_offsets: &ScrollOffsetMap,
result: &mut Vec<DisplayItemMetadata>) {
while let Some(item) = traversal.next() {
match item {
&DisplayItem::PushStackingContext(ref stacking_context_item) => {
self.hit_test_stacking_context(traversal,
&stacking_context_item.stacking_context,
translated_point,
client_point,
scroll_offsets,
result);
DisplayList::translate_point(&stacking_context_item.stacking_context,
translated_point,
client_point);
self.hit_test_contents(traversal,
translated_point,
client_point,
scroll_offsets,
result);
}
&DisplayItem::PushScrollRoot(ref item) => {
self.hit_test_scroll_root(traversal,
&item.scroll_root,
*translated_point,
client_point,
scroll_offsets,
result);
DisplayList::scroll_root(&item.scroll_root,
translated_point,
scroll_offsets);
self.hit_test_contents(traversal,
translated_point,
client_point,
scroll_offsets,
result);
}
&DisplayItem::PopStackingContext(_) | &DisplayItem::PopScrollRoot(_) => return,
_ => {
Expand All @@ -99,38 +167,15 @@ impl DisplayList {
}
}

fn hit_test_scroll_root<'a>(&self,
traversal: &mut DisplayListTraversal<'a>,
scroll_root: &ScrollRoot,
mut translated_point: Point2D<Au>,
client_point: &Point2D<Au>,
scroll_offsets: &ScrollOffsetMap,
result: &mut Vec<DisplayItemMetadata>) {
// Adjust the translated point to account for the scroll offset if
// necessary. This can only happen when WebRender is in use.
//
// We don't perform this adjustment on the root stacking context because
// the DOM-side code has already translated the point for us (e.g. in
// `Window::hit_test_query()`) by now.
if let Some(scroll_offset) = scroll_offsets.get(&scroll_root.id) {
translated_point.x -= Au::from_f32_px(scroll_offset.x);
translated_point.y -= Au::from_f32_px(scroll_offset.y);
}
self.hit_test_contents(traversal, &translated_point, client_point, scroll_offsets, result);
}

fn hit_test_stacking_context<'a>(&self,
traversal: &mut DisplayListTraversal<'a>,
stacking_context: &StackingContext,
translated_point: &Point2D<Au>,
client_point: &Point2D<Au>,
scroll_offsets: &ScrollOffsetMap,
result: &mut Vec<DisplayItemMetadata>) {
#[inline]
fn translate_point<'a>(stacking_context: &StackingContext,
translated_point: &mut Point2D<Au>,
client_point: &Point2D<Au>) {
// Convert the parent translated point into stacking context local transform space if the
// stacking context isn't fixed. If it's fixed, we need to use the client point anyway.
debug_assert!(stacking_context.context_type == StackingContextType::Real);
let is_fixed = stacking_context.scroll_policy == ScrollPolicy::Fixed;
let translated_point = if is_fixed {
*translated_point = if is_fixed {
*client_point
} else {
let point = *translated_point - stacking_context.bounds.origin;
Expand All @@ -139,8 +184,21 @@ impl DisplayList {
point.y.to_f32_px()));
Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y))
};
}

self.hit_test_contents(traversal, &translated_point, client_point, scroll_offsets, result);
#[inline]
fn scroll_root<'a>(scroll_root: &ScrollRoot,
translated_point: &mut Point2D<Au>,
scroll_offsets: &ScrollOffsetMap) {
// Adjust the translated point to account for the scroll offset if necessary.
//
// We don't perform this adjustment on the root stacking context because
// the DOM-side code has already translated the point for us (e.g. in
// `Window::hit_test_query()`) by now.
if let Some(scroll_offset) = scroll_offsets.get(&scroll_root.id) {
translated_point.x -= Au::from_f32_px(scroll_offset.x);
translated_point.y -= Au::from_f32_px(scroll_offset.y);
}
}

pub fn print(&self) {
Expand Down
21 changes: 21 additions & 0 deletions components/gfx/text/glyph.rs
Expand Up @@ -546,6 +546,27 @@ impl<'a> GlyphStore {
}
}

// Scan the glyphs for a given range until we reach a given advance. Returns the index
// and advance of the glyph in the range at the given advance, if reached. Otherwise, returns the
// the number of glyphs and the advance for the given range.
#[inline]
pub fn range_index_of_advance(&self, range: &Range<ByteIndex>, advance: Au, extra_word_spacing: Au) -> (usize, Au) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document what the return values of this function are in a comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While describing what this function returns I realized that it's more intuitive if it returns (index, current_advance) instead of (index, advance -current_advance). This just needed a slight modification in the part that invokes this.
I'll add some comments after I push the modifications.

let mut index = 0;
let mut current_advance = Au(0);
for glyph in self.iter_glyphs_for_byte_range(range) {
if glyph.char_is_space() {
current_advance += glyph.advance() + extra_word_spacing
} else {
current_advance += glyph.advance()
}
if current_advance > advance {
break;
}
index += 1;
}
(index, current_advance)
}

#[inline]
pub fn advance_for_byte_range(&self, range: &Range<ByteIndex>, extra_word_spacing: Au) -> Au {
if range.begin() == ByteIndex(0) && range.end() == self.len() {
Expand Down
15 changes: 15 additions & 0 deletions components/gfx/text/text_run.rs
Expand Up @@ -304,6 +304,21 @@ impl<'a> TextRun {
})
}

/// Returns the index in the range of the first glyph advancing over given advance
pub fn range_index_of_advance(&self, range: &Range<ByteIndex>, advance: Au) -> usize {
// TODO(Issue #199): alter advance direction for RTL
// TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text
let mut remaining = advance;
self.natural_word_slices_in_range(range)
.map(|slice| {
let (slice_index, slice_advance) =
slice.glyphs.range_index_of_advance(&slice.range, remaining, self.extra_word_spacing);
remaining -= slice_advance;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pcwalton glyphs.range_index_of_advance now returns (index, advanced) instead of (index, remaining) to make it more intuitive. Therefore we need to update remaining here.

slice_index
})
.sum()
}

/// Returns an iterator that will iterate over all slices of glyphs that represent natural
/// words in the given range.
pub fn natural_word_slices_in_range(&'a self, range: &Range<ByteIndex>)
Expand Down
16 changes: 13 additions & 3 deletions components/layout/query.rs
Expand Up @@ -20,7 +20,7 @@ use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse};
use script_layout_interface::rpc::{HitTestResponse, LayoutRPC};
use script_layout_interface::rpc::{MarginStyleResponse, NodeGeometryResponse};
use script_layout_interface::rpc::{NodeOverflowResponse, OffsetParentResponse};
use script_layout_interface::rpc::{NodeScrollRootIdResponse, ResolvedStyleResponse};
use script_layout_interface::rpc::{NodeScrollRootIdResponse, ResolvedStyleResponse, TextIndexResponse};
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
use script_traits::LayoutMsg as ConstellationMsg;
use script_traits::UntrustedNodeAddress;
Expand Down Expand Up @@ -85,6 +85,9 @@ pub struct LayoutThreadData {

/// Scroll offsets of stacking contexts. This will only be populated if WebRender is in use.
pub stacking_context_scroll_offsets: ScrollOffsetMap,

/// Index in a text fragment. We need this do determine the insertion point.
pub text_index_response: TextIndexResponse,
}

pub struct LayoutRPCImpl(pub Arc<Mutex<LayoutThreadData>>);
Expand Down Expand Up @@ -138,7 +141,7 @@ impl LayoutRPC for LayoutRPCImpl {
fn nodes_from_point(&self,
page_point: Point2D<f32>,
client_point: Point2D<f32>) -> Vec<UntrustedNodeAddress> {
let page_point = Point2D::new(Au::from_f32_px(page_point.x),
let mut page_point = Point2D::new(Au::from_f32_px(page_point.x),
Au::from_f32_px(page_point.y));
let client_point = Point2D::new(Au::from_f32_px(client_point.x),
Au::from_f32_px(client_point.y));
Expand All @@ -149,7 +152,7 @@ impl LayoutRPC for LayoutRPCImpl {
let result = match rw_data.display_list {
None => panic!("Tried to hit test without a DisplayList"),
Some(ref display_list) => {
display_list.hit_test(&page_point,
display_list.hit_test(&mut page_point,
&client_point,
&rw_data.stacking_context_scroll_offsets)
}
Expand Down Expand Up @@ -206,6 +209,12 @@ impl LayoutRPC for LayoutRPCImpl {
let rw_data = rw_data.lock().unwrap();
rw_data.margin_style_response.clone()
}

fn text_index(&self) -> TextIndexResponse {
let &LayoutRPCImpl(ref rw_data) = self;
let rw_data = rw_data.lock().unwrap();
rw_data.text_index_response.clone()
}
}

struct UnioningFragmentBorderBoxIterator {
Expand Down Expand Up @@ -581,6 +590,7 @@ impl FragmentBorderBoxIterator for ParentOffsetBorderBoxIterator {
}
}


pub fn process_node_geometry_request<N: LayoutNode>(requested_node: N, layout_root: &mut Flow)
-> Rect<i32> {
let mut iterator = FragmentLocatingFragmentIterator::new(requested_node.opaque());
Expand Down
24 changes: 21 additions & 3 deletions components/layout_thread/lib.rs
Expand Up @@ -91,6 +91,7 @@ use script::layout_wrapper::{ServoLayoutElement, ServoLayoutDocument, ServoLayou
use script_layout_interface::message::{Msg, NewLayoutThreadInfo, Reflow, ReflowQueryType, ScriptReflow};
use script_layout_interface::reporter::CSSErrorReporter;
use script_layout_interface::rpc::{LayoutRPC, MarginStyleResponse, NodeOverflowResponse, OffsetParentResponse};
use script_layout_interface::rpc::TextIndexResponse;
use script_layout_interface::wrapper_traits::LayoutNode;
use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg};
use script_traits::{StackingContextScrollState, UntrustedNodeAddress};
Expand Down Expand Up @@ -474,6 +475,7 @@ impl LayoutThread {
offset_parent_response: OffsetParentResponse::empty(),
margin_style_response: MarginStyleResponse::empty(),
stacking_context_scroll_offsets: HashMap::new(),
text_index_response: TextIndexResponse(None),
})),
error_reporter: CSSErrorReporter {
pipelineid: id,
Expand Down Expand Up @@ -1039,6 +1041,9 @@ impl LayoutThread {
ReflowQueryType::MarginStyleQuery(_) => {
rw_data.margin_style_response = MarginStyleResponse::empty();
},
ReflowQueryType::TextIndexQuery(..) => {
rw_data.text_index_response = TextIndexResponse(None);
}
ReflowQueryType::NoQuery => {}
}
return;
Expand Down Expand Up @@ -1243,7 +1248,7 @@ impl LayoutThread {
rw_data.content_boxes_response = process_content_boxes_request(node, root_flow);
},
ReflowQueryType::HitTestQuery(translated_point, client_point, update_cursor) => {
let translated_point = Point2D::new(Au::from_f32_px(translated_point.x),
let mut translated_point = Point2D::new(Au::from_f32_px(translated_point.x),
Au::from_f32_px(translated_point.y));

let client_point = Point2D::new(Au::from_f32_px(client_point.x),
Expand All @@ -1252,11 +1257,24 @@ impl LayoutThread {
let result = rw_data.display_list
.as_ref()
.expect("Tried to hit test with no display list")
.hit_test(&translated_point,
.hit_test(&mut translated_point,
&client_point,
&rw_data.stacking_context_scroll_offsets);
rw_data.hit_test_response = (result.last().cloned(), update_cursor);
},
ReflowQueryType::TextIndexQuery(node, mouse_x, mouse_y) => {
let node = unsafe { ServoLayoutNode::new(&node) };
let opaque_node = node.opaque();
let client_point = Point2D::new(Au::from_px(mouse_x),
Au::from_px(mouse_y));
rw_data.text_index_response =
TextIndexResponse(rw_data.display_list
.as_ref()
.expect("Tried to hit test with no display list")
.text_index(opaque_node,
&client_point,
&rw_data.stacking_context_scroll_offsets));
},
ReflowQueryType::NodeGeometryQuery(node) => {
let node = unsafe { ServoLayoutNode::new(&node) };
rw_data.client_rect_response = process_node_geometry_request(node, root_flow);
Expand Down Expand Up @@ -1593,7 +1611,7 @@ fn get_ua_stylesheets() -> Result<UserAgentStylesheets, &'static str> {
/// or false if it only needs stacking-relative positions.
fn reflow_query_type_needs_display_list(query_type: &ReflowQueryType) -> bool {
match *query_type {
ReflowQueryType::HitTestQuery(..) => true,
ReflowQueryType::HitTestQuery(..) | ReflowQueryType::TextIndexQuery(..) => true,
ReflowQueryType::ContentBoxQuery(_) | ReflowQueryType::ContentBoxesQuery(_) |
ReflowQueryType::NodeGeometryQuery(_) | ReflowQueryType::NodeScrollGeometryQuery(_) |
ReflowQueryType::NodeOverflowQuery(_) | ReflowQueryType::NodeScrollRootIdQuery(_) |
Expand Down