Skip to content

Commit

Permalink
Position insertion point in input field with mouse
Browse files Browse the repository at this point in the history
  • Loading branch information
fiji-flo committed Dec 7, 2016
1 parent 900cb3b commit b130521
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 38 deletions.
144 changes: 113 additions & 31 deletions components/gfx/display_list/mod.rs
Expand Up @@ -48,10 +48,88 @@ 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) => {
self.text_index_stacking_context(node,
traversal,
&stacking_context_item.stacking_context,
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);
match index {
Some(index) => result.push(index),
None => {}
}
}
},
_ => {},
}
}
}

fn text_index_stacking_context<'a>(&self,
node: OpaqueNode,
traversal: &mut DisplayListTraversal<'a>,
stacking_context: &StackingContext,
translated_point: &mut Point2D<Au>,
client_point: &Point2D<Au>,
scroll_offsets: &ScrollOffsetMap,
result: &mut Vec<usize>) {
DisplayList::translate_point(stacking_context, translated_point, client_point);

self.text_index_contents(node, traversal, translated_point, client_point, scroll_offsets, result);
}

// 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,7 +145,7 @@ 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>) {
Expand All @@ -82,12 +160,14 @@ impl DisplayList {
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 +179,26 @@ 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>,
translated_point: &mut Point2D<Au>,
client_point: &Point2D<Au>,
scroll_offsets: &ScrollOffsetMap,
result: &mut Vec<DisplayItemMetadata>) {
DisplayList::translate_point(stacking_context, translated_point, client_point);
self.hit_test_contents(traversal, translated_point, client_point, scroll_offsets, result);
}

#[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::FixedPosition;
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 +207,22 @@ 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. 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);
}
}

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 {
}
}

#[inline]
pub fn range_index_of_advance(&self, range: &Range<ByteIndex>, advance: Au, extra_word_spacing: Au) -> (usize, Au) {
let mut index = 0;
let mut current_advance = Au(0);
self.iter_glyphs_for_byte_range(range)
.take_while(|glyph| {
if glyph.char_is_space() {
current_advance += glyph.advance() + extra_word_spacing
} else {
current_advance += glyph.advance()
}
if current_advance > advance {
false
} else {
index += 1;
true
}
}).collect::<Vec<_>>();
(index, advance - 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
18 changes: 18 additions & 0 deletions components/gfx/text/text_run.rs
Expand Up @@ -304,6 +304,24 @@ 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) -> Option<usize> {
if range.is_empty() {
return Some(0);
}

// 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;
Some(self.natural_word_slices_in_range(range)
.fold(0, |index, slice| {
let (new_index, new_remaining) =
slice.glyphs.range_index_of_advance(&slice.range, remaining, self.extra_word_spacing);
remaining = new_remaining;
new_index + index
}))
}

/// 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 @@ -19,7 +19,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 @@ -90,6 +90,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 @@ -467,6 +468,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 @@ -1016,6 +1018,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 @@ -1210,7 +1215,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 @@ -1219,11 +1224,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 @@ -1561,7 +1579,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

0 comments on commit b130521

Please sign in to comment.