mirror of
https://github.com/servo/servo.git
synced 2025-07-30 10:40:27 +01:00
Position insertion point in input field with mouse
This commit is contained in:
parent
c784bc6944
commit
b40db5b55d
9 changed files with 219 additions and 50 deletions
|
@ -48,10 +48,74 @@ pub struct DisplayList {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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
|
// Return all nodes containing the point of interest, bottommost first, and
|
||||||
// respecting the `pointer-events` CSS property.
|
// respecting the `pointer-events` CSS property.
|
||||||
pub fn hit_test(&self,
|
pub fn hit_test(&self,
|
||||||
translated_point: &Point2D<Au>,
|
translated_point: &mut Point2D<Au>,
|
||||||
client_point: &Point2D<Au>,
|
client_point: &Point2D<Au>,
|
||||||
scroll_offsets: &ScrollOffsetMap)
|
scroll_offsets: &ScrollOffsetMap)
|
||||||
-> Vec<DisplayItemMetadata> {
|
-> Vec<DisplayItemMetadata> {
|
||||||
|
@ -67,27 +131,31 @@ impl DisplayList {
|
||||||
|
|
||||||
pub fn hit_test_contents<'a>(&self,
|
pub fn hit_test_contents<'a>(&self,
|
||||||
traversal: &mut DisplayListTraversal<'a>,
|
traversal: &mut DisplayListTraversal<'a>,
|
||||||
translated_point: &Point2D<Au>,
|
translated_point: &mut Point2D<Au>,
|
||||||
client_point: &Point2D<Au>,
|
client_point: &Point2D<Au>,
|
||||||
scroll_offsets: &ScrollOffsetMap,
|
scroll_offsets: &ScrollOffsetMap,
|
||||||
result: &mut Vec<DisplayItemMetadata>) {
|
result: &mut Vec<DisplayItemMetadata>) {
|
||||||
while let Some(item) = traversal.next() {
|
while let Some(item) = traversal.next() {
|
||||||
match item {
|
match item {
|
||||||
&DisplayItem::PushStackingContext(ref stacking_context_item) => {
|
&DisplayItem::PushStackingContext(ref stacking_context_item) => {
|
||||||
self.hit_test_stacking_context(traversal,
|
DisplayList::translate_point(&stacking_context_item.stacking_context,
|
||||||
&stacking_context_item.stacking_context,
|
translated_point,
|
||||||
translated_point,
|
client_point);
|
||||||
client_point,
|
self.hit_test_contents(traversal,
|
||||||
scroll_offsets,
|
translated_point,
|
||||||
result);
|
client_point,
|
||||||
|
scroll_offsets,
|
||||||
|
result);
|
||||||
}
|
}
|
||||||
&DisplayItem::PushScrollRoot(ref item) => {
|
&DisplayItem::PushScrollRoot(ref item) => {
|
||||||
self.hit_test_scroll_root(traversal,
|
DisplayList::scroll_root(&item.scroll_root,
|
||||||
&item.scroll_root,
|
translated_point,
|
||||||
*translated_point,
|
scroll_offsets);
|
||||||
client_point,
|
self.hit_test_contents(traversal,
|
||||||
scroll_offsets,
|
translated_point,
|
||||||
result);
|
client_point,
|
||||||
|
scroll_offsets,
|
||||||
|
result);
|
||||||
}
|
}
|
||||||
&DisplayItem::PopStackingContext(_) | &DisplayItem::PopScrollRoot(_) => return,
|
&DisplayItem::PopStackingContext(_) | &DisplayItem::PopScrollRoot(_) => return,
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -99,38 +167,15 @@ impl DisplayList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hit_test_scroll_root<'a>(&self,
|
#[inline]
|
||||||
traversal: &mut DisplayListTraversal<'a>,
|
fn translate_point<'a>(stacking_context: &StackingContext,
|
||||||
scroll_root: &ScrollRoot,
|
translated_point: &mut Point2D<Au>,
|
||||||
mut translated_point: Point2D<Au>,
|
client_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>) {
|
|
||||||
// Convert the parent translated point into stacking context local transform space if the
|
// 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.
|
// 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);
|
debug_assert!(stacking_context.context_type == StackingContextType::Real);
|
||||||
let is_fixed = stacking_context.scroll_policy == ScrollPolicy::Fixed;
|
let is_fixed = stacking_context.scroll_policy == ScrollPolicy::Fixed;
|
||||||
let translated_point = if is_fixed {
|
*translated_point = if is_fixed {
|
||||||
*client_point
|
*client_point
|
||||||
} else {
|
} else {
|
||||||
let point = *translated_point - stacking_context.bounds.origin;
|
let point = *translated_point - stacking_context.bounds.origin;
|
||||||
|
@ -139,8 +184,21 @@ impl DisplayList {
|
||||||
point.y.to_f32_px()));
|
point.y.to_f32_px()));
|
||||||
Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y))
|
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) {
|
pub fn print(&self) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
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]
|
#[inline]
|
||||||
pub fn advance_for_byte_range(&self, range: &Range<ByteIndex>, extra_word_spacing: Au) -> Au {
|
pub fn advance_for_byte_range(&self, range: &Range<ByteIndex>, extra_word_spacing: Au) -> Au {
|
||||||
if range.begin() == ByteIndex(0) && range.end() == self.len() {
|
if range.begin() == ByteIndex(0) && range.end() == self.len() {
|
||||||
|
|
|
@ -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;
|
||||||
|
slice_index
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns an iterator that will iterate over all slices of glyphs that represent natural
|
/// Returns an iterator that will iterate over all slices of glyphs that represent natural
|
||||||
/// words in the given range.
|
/// words in the given range.
|
||||||
pub fn natural_word_slices_in_range(&'a self, range: &Range<ByteIndex>)
|
pub fn natural_word_slices_in_range(&'a self, range: &Range<ByteIndex>)
|
||||||
|
|
|
@ -20,7 +20,7 @@ use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse};
|
||||||
use script_layout_interface::rpc::{HitTestResponse, LayoutRPC};
|
use script_layout_interface::rpc::{HitTestResponse, LayoutRPC};
|
||||||
use script_layout_interface::rpc::{MarginStyleResponse, NodeGeometryResponse};
|
use script_layout_interface::rpc::{MarginStyleResponse, NodeGeometryResponse};
|
||||||
use script_layout_interface::rpc::{NodeOverflowResponse, OffsetParentResponse};
|
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_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
|
||||||
use script_traits::LayoutMsg as ConstellationMsg;
|
use script_traits::LayoutMsg as ConstellationMsg;
|
||||||
use script_traits::UntrustedNodeAddress;
|
use script_traits::UntrustedNodeAddress;
|
||||||
|
@ -85,6 +85,9 @@ pub struct LayoutThreadData {
|
||||||
|
|
||||||
/// Scroll offsets of stacking contexts. This will only be populated if WebRender is in use.
|
/// Scroll offsets of stacking contexts. This will only be populated if WebRender is in use.
|
||||||
pub stacking_context_scroll_offsets: ScrollOffsetMap,
|
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>>);
|
pub struct LayoutRPCImpl(pub Arc<Mutex<LayoutThreadData>>);
|
||||||
|
@ -138,7 +141,7 @@ impl LayoutRPC for LayoutRPCImpl {
|
||||||
fn nodes_from_point(&self,
|
fn nodes_from_point(&self,
|
||||||
page_point: Point2D<f32>,
|
page_point: Point2D<f32>,
|
||||||
client_point: Point2D<f32>) -> Vec<UntrustedNodeAddress> {
|
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));
|
Au::from_f32_px(page_point.y));
|
||||||
let client_point = Point2D::new(Au::from_f32_px(client_point.x),
|
let client_point = Point2D::new(Au::from_f32_px(client_point.x),
|
||||||
Au::from_f32_px(client_point.y));
|
Au::from_f32_px(client_point.y));
|
||||||
|
@ -149,7 +152,7 @@ impl LayoutRPC for LayoutRPCImpl {
|
||||||
let result = match rw_data.display_list {
|
let result = match rw_data.display_list {
|
||||||
None => panic!("Tried to hit test without a DisplayList"),
|
None => panic!("Tried to hit test without a DisplayList"),
|
||||||
Some(ref display_list) => {
|
Some(ref display_list) => {
|
||||||
display_list.hit_test(&page_point,
|
display_list.hit_test(&mut page_point,
|
||||||
&client_point,
|
&client_point,
|
||||||
&rw_data.stacking_context_scroll_offsets)
|
&rw_data.stacking_context_scroll_offsets)
|
||||||
}
|
}
|
||||||
|
@ -206,6 +209,12 @@ impl LayoutRPC for LayoutRPCImpl {
|
||||||
let rw_data = rw_data.lock().unwrap();
|
let rw_data = rw_data.lock().unwrap();
|
||||||
rw_data.margin_style_response.clone()
|
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 {
|
struct UnioningFragmentBorderBoxIterator {
|
||||||
|
@ -581,6 +590,7 @@ impl FragmentBorderBoxIterator for ParentOffsetBorderBoxIterator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn process_node_geometry_request<N: LayoutNode>(requested_node: N, layout_root: &mut Flow)
|
pub fn process_node_geometry_request<N: LayoutNode>(requested_node: N, layout_root: &mut Flow)
|
||||||
-> Rect<i32> {
|
-> Rect<i32> {
|
||||||
let mut iterator = FragmentLocatingFragmentIterator::new(requested_node.opaque());
|
let mut iterator = FragmentLocatingFragmentIterator::new(requested_node.opaque());
|
||||||
|
|
|
@ -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::message::{Msg, NewLayoutThreadInfo, Reflow, ReflowQueryType, ScriptReflow};
|
||||||
use script_layout_interface::reporter::CSSErrorReporter;
|
use script_layout_interface::reporter::CSSErrorReporter;
|
||||||
use script_layout_interface::rpc::{LayoutRPC, MarginStyleResponse, NodeOverflowResponse, OffsetParentResponse};
|
use script_layout_interface::rpc::{LayoutRPC, MarginStyleResponse, NodeOverflowResponse, OffsetParentResponse};
|
||||||
|
use script_layout_interface::rpc::TextIndexResponse;
|
||||||
use script_layout_interface::wrapper_traits::LayoutNode;
|
use script_layout_interface::wrapper_traits::LayoutNode;
|
||||||
use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg};
|
use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg};
|
||||||
use script_traits::{StackingContextScrollState, UntrustedNodeAddress};
|
use script_traits::{StackingContextScrollState, UntrustedNodeAddress};
|
||||||
|
@ -474,6 +475,7 @@ impl LayoutThread {
|
||||||
offset_parent_response: OffsetParentResponse::empty(),
|
offset_parent_response: OffsetParentResponse::empty(),
|
||||||
margin_style_response: MarginStyleResponse::empty(),
|
margin_style_response: MarginStyleResponse::empty(),
|
||||||
stacking_context_scroll_offsets: HashMap::new(),
|
stacking_context_scroll_offsets: HashMap::new(),
|
||||||
|
text_index_response: TextIndexResponse(None),
|
||||||
})),
|
})),
|
||||||
error_reporter: CSSErrorReporter {
|
error_reporter: CSSErrorReporter {
|
||||||
pipelineid: id,
|
pipelineid: id,
|
||||||
|
@ -1039,6 +1041,9 @@ impl LayoutThread {
|
||||||
ReflowQueryType::MarginStyleQuery(_) => {
|
ReflowQueryType::MarginStyleQuery(_) => {
|
||||||
rw_data.margin_style_response = MarginStyleResponse::empty();
|
rw_data.margin_style_response = MarginStyleResponse::empty();
|
||||||
},
|
},
|
||||||
|
ReflowQueryType::TextIndexQuery(..) => {
|
||||||
|
rw_data.text_index_response = TextIndexResponse(None);
|
||||||
|
}
|
||||||
ReflowQueryType::NoQuery => {}
|
ReflowQueryType::NoQuery => {}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -1243,7 +1248,7 @@ impl LayoutThread {
|
||||||
rw_data.content_boxes_response = process_content_boxes_request(node, root_flow);
|
rw_data.content_boxes_response = process_content_boxes_request(node, root_flow);
|
||||||
},
|
},
|
||||||
ReflowQueryType::HitTestQuery(translated_point, client_point, update_cursor) => {
|
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));
|
Au::from_f32_px(translated_point.y));
|
||||||
|
|
||||||
let client_point = Point2D::new(Au::from_f32_px(client_point.x),
|
let client_point = Point2D::new(Au::from_f32_px(client_point.x),
|
||||||
|
@ -1252,11 +1257,24 @@ impl LayoutThread {
|
||||||
let result = rw_data.display_list
|
let result = rw_data.display_list
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("Tried to hit test with no display list")
|
.expect("Tried to hit test with no display list")
|
||||||
.hit_test(&translated_point,
|
.hit_test(&mut translated_point,
|
||||||
&client_point,
|
&client_point,
|
||||||
&rw_data.stacking_context_scroll_offsets);
|
&rw_data.stacking_context_scroll_offsets);
|
||||||
rw_data.hit_test_response = (result.last().cloned(), update_cursor);
|
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) => {
|
ReflowQueryType::NodeGeometryQuery(node) => {
|
||||||
let node = unsafe { ServoLayoutNode::new(&node) };
|
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||||
rw_data.client_rect_response = process_node_geometry_request(node, root_flow);
|
rw_data.client_rect_response = process_node_geometry_request(node, root_flow);
|
||||||
|
@ -1593,7 +1611,7 @@ fn get_ua_stylesheets() -> Result<UserAgentStylesheets, &'static str> {
|
||||||
/// or false if it only needs stacking-relative positions.
|
/// or false if it only needs stacking-relative positions.
|
||||||
fn reflow_query_type_needs_display_list(query_type: &ReflowQueryType) -> bool {
|
fn reflow_query_type_needs_display_list(query_type: &ReflowQueryType) -> bool {
|
||||||
match *query_type {
|
match *query_type {
|
||||||
ReflowQueryType::HitTestQuery(..) => true,
|
ReflowQueryType::HitTestQuery(..) | ReflowQueryType::TextIndexQuery(..) => true,
|
||||||
ReflowQueryType::ContentBoxQuery(_) | ReflowQueryType::ContentBoxesQuery(_) |
|
ReflowQueryType::ContentBoxQuery(_) | ReflowQueryType::ContentBoxesQuery(_) |
|
||||||
ReflowQueryType::NodeGeometryQuery(_) | ReflowQueryType::NodeScrollGeometryQuery(_) |
|
ReflowQueryType::NodeGeometryQuery(_) | ReflowQueryType::NodeScrollGeometryQuery(_) |
|
||||||
ReflowQueryType::NodeOverflowQuery(_) | ReflowQueryType::NodeScrollRootIdQuery(_) |
|
ReflowQueryType::NodeOverflowQuery(_) | ReflowQueryType::NodeScrollRootIdQuery(_) |
|
||||||
|
|
|
@ -11,6 +11,8 @@ use dom::bindings::codegen::Bindings::FileListBinding::FileListMethods;
|
||||||
use dom::bindings::codegen::Bindings::HTMLInputElementBinding;
|
use dom::bindings::codegen::Bindings::HTMLInputElementBinding;
|
||||||
use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
|
use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
|
||||||
use dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods;
|
use dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods;
|
||||||
|
use dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
|
||||||
|
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
|
||||||
use dom::bindings::error::{Error, ErrorResult};
|
use dom::bindings::error::{Error, ErrorResult};
|
||||||
use dom::bindings::inheritance::Castable;
|
use dom::bindings::inheritance::Castable;
|
||||||
use dom::bindings::js::{JS, LayoutJS, MutNullableJS, Root, RootedReference};
|
use dom::bindings::js::{JS, LayoutJS, MutNullableJS, Root, RootedReference};
|
||||||
|
@ -27,6 +29,7 @@ use dom::htmlfieldsetelement::HTMLFieldSetElement;
|
||||||
use dom::htmlformelement::{FormControl, FormDatum, FormDatumValue, FormSubmitter, HTMLFormElement};
|
use dom::htmlformelement::{FormControl, FormDatum, FormDatumValue, FormSubmitter, HTMLFormElement};
|
||||||
use dom::htmlformelement::{ResetFrom, SubmittedFrom};
|
use dom::htmlformelement::{ResetFrom, SubmittedFrom};
|
||||||
use dom::keyboardevent::KeyboardEvent;
|
use dom::keyboardevent::KeyboardEvent;
|
||||||
|
use dom::mouseevent::MouseEvent;
|
||||||
use dom::node::{Node, NodeDamage, UnbindContext};
|
use dom::node::{Node, NodeDamage, UnbindContext};
|
||||||
use dom::node::{document_from_node, window_from_node};
|
use dom::node::{document_from_node, window_from_node};
|
||||||
use dom::nodelist::NodeList;
|
use dom::nodelist::NodeList;
|
||||||
|
@ -39,6 +42,7 @@ use mime_guess;
|
||||||
use net_traits::{CoreResourceMsg, IpcSend};
|
use net_traits::{CoreResourceMsg, IpcSend};
|
||||||
use net_traits::blob_url_store::get_blob_origin;
|
use net_traits::blob_url_store::get_blob_origin;
|
||||||
use net_traits::filemanager_thread::{FileManagerThreadMsg, FilterPattern};
|
use net_traits::filemanager_thread::{FileManagerThreadMsg, FilterPattern};
|
||||||
|
use script_layout_interface::rpc::TextIndexResponse;
|
||||||
use script_traits::ScriptMsg as ConstellationMsg;
|
use script_traits::ScriptMsg as ConstellationMsg;
|
||||||
use servo_atoms::Atom;
|
use servo_atoms::Atom;
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
|
@ -1088,6 +1092,33 @@ impl VirtualMethods for HTMLInputElement {
|
||||||
//TODO: set the editing position for text inputs
|
//TODO: set the editing position for text inputs
|
||||||
|
|
||||||
document_from_node(self).request_focus(self.upcast());
|
document_from_node(self).request_focus(self.upcast());
|
||||||
|
if (self.input_type.get() == InputType::InputText ||
|
||||||
|
self.input_type.get() == InputType::InputPassword) &&
|
||||||
|
// Check if we display a placeholder. Layout doesn't know about this.
|
||||||
|
!self.textinput.borrow().is_empty() {
|
||||||
|
if let Some(mouse_event) = event.downcast::<MouseEvent>() {
|
||||||
|
// dispatch_key_event (document.rs) triggers a click event when releasing
|
||||||
|
// the space key. There's no nice way to catch this so let's use this for
|
||||||
|
// now.
|
||||||
|
if !(mouse_event.ScreenX() == 0 && mouse_event.ScreenY() == 0 &&
|
||||||
|
mouse_event.GetRelatedTarget().is_none()) {
|
||||||
|
let window = window_from_node(self);
|
||||||
|
let translated_x = mouse_event.ClientX() + window.PageXOffset();
|
||||||
|
let translated_y = mouse_event.ClientY() + window.PageYOffset();
|
||||||
|
let TextIndexResponse(index) = window.text_index_query(
|
||||||
|
self.upcast::<Node>().to_trusted_node_address(),
|
||||||
|
translated_x,
|
||||||
|
translated_y
|
||||||
|
);
|
||||||
|
if let Some(i) = index {
|
||||||
|
self.textinput.borrow_mut().edit_point.index = i as usize;
|
||||||
|
// trigger redraw
|
||||||
|
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
|
||||||
|
event.PreventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if event.type_() == atom!("keydown") && !event.DefaultPrevented() &&
|
} else if event.type_() == atom!("keydown") && !event.DefaultPrevented() &&
|
||||||
(self.input_type.get() == InputType::InputText ||
|
(self.input_type.get() == InputType::InputText ||
|
||||||
self.input_type.get() == InputType::InputPassword) {
|
self.input_type.get() == InputType::InputPassword) {
|
||||||
|
|
|
@ -68,7 +68,7 @@ use script_layout_interface::message::{Msg, Reflow, ReflowQueryType, ScriptReflo
|
||||||
use script_layout_interface::reporter::CSSErrorReporter;
|
use script_layout_interface::reporter::CSSErrorReporter;
|
||||||
use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC};
|
use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC};
|
||||||
use script_layout_interface::rpc::{MarginStyleResponse, NodeScrollRootIdResponse};
|
use script_layout_interface::rpc::{MarginStyleResponse, NodeScrollRootIdResponse};
|
||||||
use script_layout_interface::rpc::ResolvedStyleResponse;
|
use script_layout_interface::rpc::{ResolvedStyleResponse, TextIndexResponse};
|
||||||
use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThreadEventCategory};
|
use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThreadEventCategory};
|
||||||
use script_thread::{MainThreadScriptChan, MainThreadScriptMsg, Runnable, RunnableWrapper};
|
use script_thread::{MainThreadScriptChan, MainThreadScriptMsg, Runnable, RunnableWrapper};
|
||||||
use script_thread::SendableMainThreadScriptChan;
|
use script_thread::SendableMainThreadScriptChan;
|
||||||
|
@ -1362,6 +1362,15 @@ impl Window {
|
||||||
self.layout_rpc.margin_style()
|
self.layout_rpc.margin_style()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn text_index_query(&self, node: TrustedNodeAddress, mouse_x: i32, mouse_y: i32) -> TextIndexResponse {
|
||||||
|
if !self.reflow(ReflowGoal::ForScriptQuery,
|
||||||
|
ReflowQueryType::TextIndexQuery(node, mouse_x, mouse_y),
|
||||||
|
ReflowReason::Query) {
|
||||||
|
return TextIndexResponse(None);
|
||||||
|
}
|
||||||
|
self.layout_rpc.text_index()
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
pub fn init_browsing_context(&self, browsing_context: &BrowsingContext) {
|
pub fn init_browsing_context(&self, browsing_context: &BrowsingContext) {
|
||||||
assert!(self.browsing_context.get().is_none());
|
assert!(self.browsing_context.get().is_none());
|
||||||
|
@ -1710,6 +1719,7 @@ fn debug_reflow_events(id: PipelineId, goal: &ReflowGoal, query_type: &ReflowQue
|
||||||
ReflowQueryType::ResolvedStyleQuery(_, _, _) => "\tResolvedStyleQuery",
|
ReflowQueryType::ResolvedStyleQuery(_, _, _) => "\tResolvedStyleQuery",
|
||||||
ReflowQueryType::OffsetParentQuery(_n) => "\tOffsetParentQuery",
|
ReflowQueryType::OffsetParentQuery(_n) => "\tOffsetParentQuery",
|
||||||
ReflowQueryType::MarginStyleQuery(_n) => "\tMarginStyleQuery",
|
ReflowQueryType::MarginStyleQuery(_n) => "\tMarginStyleQuery",
|
||||||
|
ReflowQueryType::TextIndexQuery(..) => "\tTextIndexQuery",
|
||||||
});
|
});
|
||||||
|
|
||||||
debug_msg.push_str(match *reason {
|
debug_msg.push_str(match *reason {
|
||||||
|
|
|
@ -100,6 +100,7 @@ pub enum ReflowQueryType {
|
||||||
ResolvedStyleQuery(TrustedNodeAddress, Option<PseudoElement>, PropertyId),
|
ResolvedStyleQuery(TrustedNodeAddress, Option<PseudoElement>, PropertyId),
|
||||||
OffsetParentQuery(TrustedNodeAddress),
|
OffsetParentQuery(TrustedNodeAddress),
|
||||||
MarginStyleQuery(TrustedNodeAddress),
|
MarginStyleQuery(TrustedNodeAddress),
|
||||||
|
TextIndexQuery(TrustedNodeAddress, i32, i32),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information needed for a reflow.
|
/// Information needed for a reflow.
|
||||||
|
|
|
@ -39,6 +39,8 @@ pub trait LayoutRPC {
|
||||||
fn margin_style(&self) -> MarginStyleResponse;
|
fn margin_style(&self) -> MarginStyleResponse;
|
||||||
|
|
||||||
fn nodes_from_point(&self, page_point: Point2D<f32>, client_point: Point2D<f32>) -> Vec<UntrustedNodeAddress>;
|
fn nodes_from_point(&self, page_point: Point2D<f32>, client_point: Point2D<f32>) -> Vec<UntrustedNodeAddress>;
|
||||||
|
|
||||||
|
fn text_index(&self) -> TextIndexResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ContentBoxResponse(pub Rect<Au>);
|
pub struct ContentBoxResponse(pub Rect<Au>);
|
||||||
|
@ -92,3 +94,6 @@ impl MarginStyleResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TextIndexResponse(pub Option<usize>);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue